feat: Implementación completa Fase 4 - Sistema de monitoreo con API REST y WebSocket
✨ Nuevas funcionalidades: - API REST unificada en puerto 8080 (eliminado CORS) - WebSocket para logs en tiempo real desde journalctl - Integración completa con systemd para gestión de servicios - Escaneo automático de procesos Node.js y Python - Rate limiting (1 operación/segundo por app) - Interface web moderna con Tailwind CSS (tema oscuro) - Documentación API estilo Swagger completamente en español 🎨 Interface Web (todas las páginas en español): - Dashboard con estadísticas en tiempo real - Visor de escaneo de procesos con filtros - Formulario de registro de aplicaciones con variables de entorno - Visor de logs en tiempo real con WebSocket y sidebar - Página de selección de apps detectadas - Documentación completa de API REST 🏗️ Arquitectura: - Módulo models: ServiceConfig, ManagedApp, AppStatus - Módulo systemd: wrapper de systemctl, generador de .service, parser - Módulo orchestrator: AppManager, LifecycleManager con validaciones - Módulo api: handlers REST, WebSocket manager, DTOs - Servidor unificado en puerto 8080 (Web + API + WS) 🔧 Mejoras técnicas: - Eliminación de CORS mediante servidor unificado - Separación clara frontend/backend con carga dinámica - Thread-safe con Arc<DashMap> para estado compartido - Reconciliación de estados: sysinfo vs systemd - Validaciones de paths, usuarios y configuraciones - Manejo robusto de errores con thiserror 📝 Documentación: - README.md actualizado con arquitectura completa - EJEMPLOS.md con casos de uso detallados - ESTADO_PROYECTO.md con progreso de Fase 4 - API docs interactiva en /api-docs - Script de despliegue mejorado con health checks 🚀 Producción: - Deployment script con validaciones - Health checks y rollback capability - Configuración de sudoers para systemctl - Hardening de seguridad en servicios systemd
This commit is contained in:
565
web/api-docs.html
Normal file
565
web/api-docs.html
Normal file
@@ -0,0 +1,565 @@
|
||||
<!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>
|
||||
<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">http://localhost:8080/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(`http://localhost:8080${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>
|
||||
594
web/index.html
594
web/index.html
@@ -1,78 +1,522 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SIAX Emergency Panel</title>
|
||||
<style>
|
||||
body {
|
||||
background: #0f172a;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
padding: 40px;
|
||||
}
|
||||
h1 {
|
||||
color: #3b82f6;
|
||||
}
|
||||
.status-online {
|
||||
color: #22c55e;
|
||||
font-weight: bold;
|
||||
}
|
||||
.server-info {
|
||||
color: #94a3b8;
|
||||
}
|
||||
.button-container {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #3b82f6;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
.btn-success {
|
||||
background: #22c55e;
|
||||
}
|
||||
.btn-success:hover {
|
||||
background: #16a34a;
|
||||
}
|
||||
.btn-warning {
|
||||
background: #f59e0b;
|
||||
}
|
||||
.btn-warning:hover {
|
||||
background: #d97706;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🚨 SIAX EMERGENCY PANEL</h1>
|
||||
<p>Estado del Agente: <span class="status-online">● ONLINE</span></p>
|
||||
<p class="server-info">Servidor: {{SERVER_NAME}}</p>
|
||||
<!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>Panel de Monitoreo</title>
|
||||
<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=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script id="tailwind-config">
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#137fec",
|
||||
"background-light": "#f6f7f8",
|
||||
"background-dark": "#101922",
|
||||
},
|
||||
fontFamily: {
|
||||
display: ["Inter"],
|
||||
},
|
||||
borderRadius: {
|
||||
DEFAULT: "0.25rem",
|
||||
lg: "0.5rem",
|
||||
xl: "0.75rem",
|
||||
full: "9999px",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
"FILL" 0,
|
||||
"wght" 400,
|
||||
"GRAD" 0,
|
||||
"opsz" 24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
class="bg-background-light dark:bg-background-dark text-slate-900 dark:text-slate-100 min-h-screen"
|
||||
>
|
||||
<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">
|
||||
<div
|
||||
class="size-8 bg-primary rounded-lg flex items-center justify-center text-white"
|
||||
>
|
||||
<span class="material-symbols-outlined"
|
||||
>monitoring</span
|
||||
>
|
||||
</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">
|
||||
<a
|
||||
class="text-primary text-sm font-semibold leading-normal"
|
||||
href="/"
|
||||
>Inicio</a
|
||||
>
|
||||
<a
|
||||
class="text-slate-600 dark:text-slate-400 hover:text-white text-sm font-medium transition-colors"
|
||||
href="/scan"
|
||||
>
|
||||
Escanear
|
||||
</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"
|
||||
>
|
||||
Registros
|
||||
</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>
|
||||
<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>Registrar App</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("https://lh3.googleusercontent.com/aida-public/AB6AXuCT0iINTncUFHp353HCJXRR5C0OKbSp_7IBOVNoDU07yuF2aToQQdnXNOeGI9RLUjVBsVNcU--ZoTMY90FFJvrQvYvRzKvq-CFCzBlVkCeoi5AgG84cB71wW0NIMg626M_sCjmDjxqmAJwIbkAcSmSlAg3TUThW1U2A3StNVgqFXEpgFbpJcU5nxLs6vuRkfYR1kIXcV44TQpgOosbsjSB1Pk1UTOQJ_OEcQtY-5c3FJw7gXBDxlp6y3jsY3rBm0xWGJi8NWnrUrhpl");
|
||||
"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main class="max-w-[1200px] mx-auto w-full px-4 lg:px-10 py-8">
|
||||
<!-- Page Heading -->
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-between gap-4 mb-8"
|
||||
>
|
||||
<div>
|
||||
<h1
|
||||
class="text-slate-900 dark:text-white text-3xl font-black tracking-tight"
|
||||
>
|
||||
Dashboard Index
|
||||
</h1>
|
||||
<p class="text-slate-500 text-sm mt-1">
|
||||
Monitoreo de salud del sistema y procesos en tiempo
|
||||
real - Server: {{SERVER_NAME}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
class="flex items-center gap-2 rounded-lg h-10 px-4 bg-slate-200 dark:bg-slate-800 text-slate-700 dark:text-white text-sm font-bold transition-colors hover:bg-slate-700"
|
||||
onclick="window.location.href = '/scan'"
|
||||
>
|
||||
<span class="material-symbols-outlined text-lg"
|
||||
>refresh</span
|
||||
>
|
||||
<span>Escanear Sistema</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center gap-2 rounded-lg h-10 px-4 bg-primary text-white text-sm font-bold transition-opacity hover:opacity-90 lg:hidden"
|
||||
onclick="window.location.href = '/register'"
|
||||
>
|
||||
<span class="material-symbols-outlined text-lg"
|
||||
>add</span
|
||||
>
|
||||
<span>Registrar</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stats Row -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800/50 rounded-xl p-6 border border-slate-200 dark:border-slate-800"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p
|
||||
class="text-slate-500 dark:text-slate-400 text-sm font-medium"
|
||||
>
|
||||
Uso CPU
|
||||
</p>
|
||||
<span class="material-symbols-outlined text-primary"
|
||||
>speed</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-end gap-2">
|
||||
<p
|
||||
class="text-slate-900 dark:text-white text-3xl font-bold"
|
||||
>
|
||||
24.8%
|
||||
</p>
|
||||
<p
|
||||
class="text-red-500 text-sm font-semibold mb-1 flex items-center"
|
||||
>
|
||||
<span class="material-symbols-outlined text-sm"
|
||||
>trending_up</span
|
||||
>+2.4%
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="mt-4 w-full bg-slate-200 dark:bg-slate-700 rounded-full h-1.5"
|
||||
>
|
||||
<div
|
||||
class="bg-primary h-1.5 rounded-full"
|
||||
style="width: 24.8%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800/50 rounded-xl p-6 border border-slate-200 dark:border-slate-800"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p
|
||||
class="text-slate-500 dark:text-slate-400 text-sm font-medium"
|
||||
>
|
||||
Consumo de Memoria
|
||||
</p>
|
||||
<span class="material-symbols-outlined text-primary"
|
||||
>memory</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-end gap-2">
|
||||
<p
|
||||
class="text-slate-900 dark:text-white text-3xl font-bold"
|
||||
>
|
||||
12.4 GB
|
||||
</p>
|
||||
<p
|
||||
class="text-emerald-500 text-sm font-semibold mb-1 flex items-center"
|
||||
>
|
||||
<span class="material-symbols-outlined text-sm"
|
||||
>trending_down</span
|
||||
>-0.5%
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-slate-500 text-xs mt-1">
|
||||
of 32 GB Total RAM
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800/50 rounded-xl p-6 border border-slate-200 dark:border-slate-800"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p
|
||||
class="text-slate-500 dark:text-slate-400 text-sm font-medium"
|
||||
>
|
||||
Procesos Activos
|
||||
</p>
|
||||
<span class="material-symbols-outlined text-primary"
|
||||
>apps</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-end gap-2">
|
||||
<p
|
||||
class="text-slate-900 dark:text-white text-3xl font-bold"
|
||||
id="app-count"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p
|
||||
class="text-emerald-500 text-sm font-semibold mb-1 flex items-center"
|
||||
>
|
||||
<span class="material-symbols-outlined text-sm"
|
||||
>add</span
|
||||
>monitored
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-1 mt-4">
|
||||
<span
|
||||
class="size-2 rounded-full bg-emerald-500"
|
||||
></span>
|
||||
<span
|
||||
class="size-2 rounded-full bg-emerald-500"
|
||||
></span>
|
||||
<span
|
||||
class="size-2 rounded-full bg-emerald-500"
|
||||
></span>
|
||||
<span
|
||||
class="size-2 rounded-full bg-amber-500"
|
||||
></span>
|
||||
<span
|
||||
class="size-2 rounded-full bg-emerald-500"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Aplicaciones Recientes Section -->
|
||||
<div
|
||||
class="bg-white dark:bg-slate-800/30 rounded-xl border border-slate-200 dark:border-slate-800 overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="p-6 border-b border-slate-200 dark:border-slate-800 flex flex-col sm:flex-row sm:items-center justify-between gap-4"
|
||||
>
|
||||
<h2
|
||||
class="text-slate-900 dark:text-white text-xl font-bold"
|
||||
>
|
||||
Aplicaciones Recientes
|
||||
</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="relative flex-1 sm:w-64">
|
||||
<span
|
||||
class="absolute inset-y-0 left-0 flex items-center pl-3 text-slate-500"
|
||||
>
|
||||
<span
|
||||
class="material-symbols-outlined text-sm"
|
||||
>filter_list</span
|
||||
>
|
||||
</span>
|
||||
<input
|
||||
class="form-input w-full rounded-lg border border-slate-200 dark:border-slate-700 bg-transparent text-sm py-1.5 pl-10 pr-4 placeholder:text-slate-500 focus:ring-1 focus:ring-primary"
|
||||
placeholder="Filtrar por estado o nombre..."
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto" id="apps-table-container">
|
||||
<table class="w-full text-left border-collapse">
|
||||
<thead>
|
||||
<tr
|
||||
class="bg-slate-50 dark:bg-slate-800/50 text-slate-500 dark:text-slate-400 text-xs font-semibold uppercase tracking-wider"
|
||||
>
|
||||
<th class="px-6 py-4">Nombre de App</th>
|
||||
<th class="px-6 py-4">Estado</th>
|
||||
<th class="px-6 py-4">CPU %</th>
|
||||
<th class="px-6 py-4">Mem %</th>
|
||||
<th class="px-6 py-4">Tiempo Activo</th>
|
||||
<th class="px-6 py-4 text-right">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody
|
||||
class="divide-y divide-slate-200 dark:divide-slate-800"
|
||||
id="apps-tbody"
|
||||
>
|
||||
<tr>
|
||||
<td
|
||||
colspan="6"
|
||||
class="px-6 py-8 text-center text-slate-500"
|
||||
>
|
||||
<span
|
||||
class="material-symbols-outlined text-4xl mb-2"
|
||||
>hourglass_empty</span
|
||||
>
|
||||
<p>Cargando aplicaciones...</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div
|
||||
class="p-4 bg-slate-50 dark:bg-slate-800/50 border-t border-slate-200 dark:border-slate-800 text-center"
|
||||
>
|
||||
<a
|
||||
class="text-primary text-sm font-bold hover:underline cursor-pointer"
|
||||
onclick="window.location.href = '/scan'"
|
||||
>Ver Todas las Aplicaciones</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Quick Action Links -->
|
||||
<div class="mt-10 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div
|
||||
class="flex items-start gap-4 p-5 rounded-xl border-2 border-dashed border-slate-200 dark:border-slate-800 bg-transparent hover:border-primary/50 transition-colors cursor-pointer group"
|
||||
onclick="window.location.href = '/register'"
|
||||
>
|
||||
<div
|
||||
class="size-12 rounded-full bg-primary/10 flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-white transition-all"
|
||||
>
|
||||
<span class="material-symbols-outlined"
|
||||
>add_to_queue</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="text-slate-900 dark:text-white font-bold text-lg leading-tight"
|
||||
>
|
||||
Register New Service
|
||||
</h3>
|
||||
<p class="text-slate-500 text-sm">
|
||||
Manually add a binary or process to the
|
||||
monitoring queue.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-start gap-4 p-5 rounded-xl border-2 border-dashed border-slate-200 dark:border-slate-800 bg-transparent hover:border-primary/50 transition-colors cursor-pointer group"
|
||||
onclick="window.location.href = '/logs'"
|
||||
>
|
||||
<div
|
||||
class="size-12 rounded-full bg-primary/10 flex items-center justify-center text-primary group-hover:bg-primary group-hover:text-white transition-all"
|
||||
>
|
||||
<span class="material-symbols-outlined"
|
||||
>history</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<h3
|
||||
class="text-slate-900 dark:text-white font-bold text-lg leading-tight"
|
||||
>
|
||||
View Event Logs
|
||||
</h3>
|
||||
<p class="text-slate-500 text-sm">
|
||||
Review detailed historical data and error
|
||||
reports from across your stack.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer
|
||||
class="mt-auto border-t border-slate-200 dark:border-slate-800 py-6"
|
||||
>
|
||||
<div
|
||||
class="max-w-[1200px] mx-auto px-4 lg:px-10 flex flex-col sm:flex-row justify-between items-center gap-4 text-slate-500 text-xs font-medium"
|
||||
>
|
||||
<p>
|
||||
© 2024 SIAX Monitor Inc. Todos los procesos del sistema
|
||||
rastreados.
|
||||
</p>
|
||||
<div class="flex gap-6">
|
||||
<a class="hover:text-primary" href="#"
|
||||
>Términos de Servicio</a
|
||||
>
|
||||
<a class="hover:text-primary" href="#"
|
||||
>Política de Privacidad</a
|
||||
>
|
||||
<a class="hover:text-primary" href="#"
|
||||
>Documentación de API</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
async function loadApps() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
"http://localhost:8080/api/apps",
|
||||
);
|
||||
const result = await response.json();
|
||||
|
||||
<div class="button-container">
|
||||
<a href="/scan" class="btn btn-primary">
|
||||
🔍 Escanear Sistema
|
||||
</a>
|
||||
if (result.success && result.data && result.data.apps) {
|
||||
document.getElementById("app-count").textContent =
|
||||
result.data.total || 0;
|
||||
|
||||
<a href="/select" class="btn btn-success">
|
||||
⚙️ Gestionar Procesos
|
||||
</a>
|
||||
if (result.data.apps && result.data.apps.length > 0) {
|
||||
displayApps(result.data.apps);
|
||||
} else {
|
||||
displayEmpty();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
displayEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
<a href="/logs" class="btn btn-warning">
|
||||
📋 Ver Logs
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
function displayApps(apps) {
|
||||
const tbody = document.getElementById("apps-tbody");
|
||||
tbody.innerHTML = apps
|
||||
.map(
|
||||
(app) => `
|
||||
<tr class="hover:bg-slate-50 dark:hover:bg-slate-800/40 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="size-8 rounded bg-slate-100 dark:bg-slate-700 flex items-center justify-center">
|
||||
<span class="material-symbols-outlined text-primary text-lg">terminal</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-slate-900 dark:text-white font-semibold text-sm">${app}</p>
|
||||
<p class="text-slate-500 text-xs">Servicio</p>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-xs font-medium bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-400">
|
||||
<span class="size-1.5 rounded-full bg-slate-400"></span>
|
||||
Unknown
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm">-</td>
|
||||
<td class="px-6 py-4 text-sm">-</td>
|
||||
<td class="px-6 py-4 text-sm text-slate-500">-</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<button class="text-slate-400 hover:text-white transition-colors">
|
||||
<span class="material-symbols-outlined">more_vert</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function displayEmpty() {
|
||||
const tbody = document.getElementById("apps-tbody");
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-8 text-center text-slate-500">
|
||||
<span class="material-symbols-outlined text-4xl mb-2">inbox</span>
|
||||
<p>No hay aplicaciones registradas aún</p>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
window.addEventListener("DOMContentLoaded", loadApps);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
697
web/logs.html
697
web/logs.html
@@ -1,246 +1,471 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Logs del Sistema - SIAX</title>
|
||||
<style>
|
||||
body {
|
||||
background: #0f172a;
|
||||
color: white;
|
||||
font-family: 'Courier New', monospace;
|
||||
padding: 40px;
|
||||
margin: 0;
|
||||
}
|
||||
h1 {
|
||||
color: #3b82f6;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
.controls {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
.btn {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-primary {
|
||||
background: #3b82f6;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
.btn-danger {
|
||||
background: #ef4444;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: #64748b;
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #475569;
|
||||
}
|
||||
.stats {
|
||||
background: #1e293b;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
.stat-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.stat-info {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
border: 1px solid #3b82f6;
|
||||
}
|
||||
.stat-warning {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
border: 1px solid #f59e0b;
|
||||
}
|
||||
.stat-error {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
.stat-critical {
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
border: 1px solid #dc2626;
|
||||
}
|
||||
.stat-number {
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.log-entry {
|
||||
background: #1e293b;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.log-info {
|
||||
border-left-color: #3b82f6;
|
||||
}
|
||||
.log-warning {
|
||||
border-left-color: #f59e0b;
|
||||
}
|
||||
.log-error {
|
||||
border-left-color: #ef4444;
|
||||
}
|
||||
.log-critical {
|
||||
border-left-color: #dc2626;
|
||||
background: rgba(220, 38, 38, 0.1);
|
||||
}
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.log-timestamp {
|
||||
color: #94a3b8;
|
||||
font-size: 11px;
|
||||
}
|
||||
.log-level {
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.log-module {
|
||||
color: #60a5fa;
|
||||
}
|
||||
.log-message {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.log-details {
|
||||
background: #0f172a;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-top: 8px;
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
}
|
||||
.no-logs {
|
||||
background: #1e293b;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
border-radius: 8px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
.filter-bar {
|
||||
background: #1e293b;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.filter-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.filter-checkbox input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.filter-checkbox label {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>📋 Logs del Sistema SIAX</h1>
|
||||
<!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>Visor de Registros - SIAX Monitor</title>
|
||||
<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;
|
||||
}
|
||||
</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="/select"
|
||||
>Agregar Detectada</a
|
||||
>
|
||||
<a
|
||||
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||
href="/register"
|
||||
>Nueva App</a
|
||||
>
|
||||
<a
|
||||
class="text-primary text-sm font-medium border-b-2 border-primary pb-1"
|
||||
href="/logs"
|
||||
>Registros</a
|
||||
>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="location.reload()" class="btn btn-primary">🔄 Refrescar</button>
|
||||
<button onclick="clearLogs()" class="btn btn-danger">🗑️ Limpiar Logs</button>
|
||||
<a href="/" class="btn btn-secondary">← Volver al Panel</a>
|
||||
</div>
|
||||
<div class="flex-1 flex max-w-[1400px] mx-auto w-full">
|
||||
<!-- Sidebar - App List -->
|
||||
<aside
|
||||
class="w-64 border-r border-[#283039] bg-[#161f2a] p-4 space-y-4 overflow-y-auto"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-white font-bold text-sm">Aplicaciones</h3>
|
||||
<button
|
||||
onclick="loadApps()"
|
||||
class="text-[#9dabb9] hover:text-white transition-colors"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]"
|
||||
>refresh</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
{{STATS}}
|
||||
</div>
|
||||
<div id="app-list" class="space-y-2">
|
||||
<!-- Apps will be loaded here -->
|
||||
</div>
|
||||
|
||||
<div class="filter-bar">
|
||||
<span style="color: #60a5fa; font-weight: bold;">Filtrar por nivel:</span>
|
||||
<div class="filter-checkbox">
|
||||
<input type="checkbox" id="filter-info" checked onchange="filterLogs()">
|
||||
<label for="filter-info">ℹ️ Info</label>
|
||||
</div>
|
||||
<div class="filter-checkbox">
|
||||
<input type="checkbox" id="filter-warning" checked onchange="filterLogs()">
|
||||
<label for="filter-warning">⚠️ Warning</label>
|
||||
</div>
|
||||
<div class="filter-checkbox">
|
||||
<input type="checkbox" id="filter-error" checked onchange="filterLogs()">
|
||||
<label for="filter-error">❌ Error</label>
|
||||
</div>
|
||||
<div class="filter-checkbox">
|
||||
<input type="checkbox" id="filter-critical" checked onchange="filterLogs()">
|
||||
<label for="filter-critical">🔥 Critical</label>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Loading State -->
|
||||
<div id="sidebar-loading" class="hidden text-center py-8">
|
||||
<span
|
||||
class="material-symbols-outlined text-primary text-3xl animate-spin"
|
||||
>progress_activity</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div id="logs-container">
|
||||
{{LOGS}}
|
||||
</div>
|
||||
<!-- Empty State -->
|
||||
<div id="sidebar-empty" class="hidden text-center py-8">
|
||||
<span
|
||||
class="material-symbols-outlined text-[#9dabb9] text-3xl"
|
||||
>inbox</span
|
||||
>
|
||||
<p class="text-[#9dabb9] text-xs mt-2">
|
||||
No hay apps registradas
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<script>
|
||||
function clearLogs() {
|
||||
if (confirm('¿Estás seguro de que quieres eliminar todos los logs?')) {
|
||||
fetch('/clear-logs', { method: 'POST' })
|
||||
.then(() => location.reload())
|
||||
.catch(err => alert('Error al limpiar logs: ' + err));
|
||||
}
|
||||
}
|
||||
<!-- Main Content - Log Viewer -->
|
||||
<main class="flex-1 flex flex-col">
|
||||
<!-- Log Header -->
|
||||
<div class="border-b border-[#283039] bg-[#161f2a] p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-primary"
|
||||
>terminal</span
|
||||
>
|
||||
<div>
|
||||
<h1
|
||||
class="text-white text-lg font-bold"
|
||||
id="current-app-name"
|
||||
>
|
||||
Selecciona una aplicación
|
||||
</h1>
|
||||
<p
|
||||
class="text-[#9dabb9] text-xs"
|
||||
id="connection-status"
|
||||
>
|
||||
Not connected
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
id="auto-scroll-btn"
|
||||
onclick="toggleAutoScroll()"
|
||||
class="flex items-center gap-2 px-3 py-2 bg-primary/20 border border-primary/30 rounded-lg text-primary text-sm font-medium hover:bg-primary/30 transition-colors"
|
||||
>
|
||||
<span
|
||||
class="material-symbols-outlined text-[18px]"
|
||||
>swap_vert</span
|
||||
>
|
||||
Auto-scroll: ON
|
||||
</button>
|
||||
<button
|
||||
onclick="clearLogViewer()"
|
||||
class="flex items-center gap-2 px-3 py-2 bg-red-500/20 border border-red-500/30 rounded-lg text-red-400 text-sm font-medium hover:bg-red-500/30 transition-colors"
|
||||
>
|
||||
<span
|
||||
class="material-symbols-outlined text-[18px]"
|
||||
>delete</span
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
function filterLogs() {
|
||||
const showInfo = document.getElementById('filter-info').checked;
|
||||
const showWarning = document.getElementById('filter-warning').checked;
|
||||
const showError = document.getElementById('filter-error').checked;
|
||||
const showCritical = document.getElementById('filter-critical').checked;
|
||||
<!-- Terminal Log Output -->
|
||||
<div
|
||||
class="flex-1 bg-[#0a0f16] overflow-y-auto p-4 font-mono text-sm"
|
||||
id="log-terminal"
|
||||
>
|
||||
<div id="log-container" class="space-y-1">
|
||||
<!-- Welcome Message -->
|
||||
<div class="text-[#9dabb9] opacity-50">
|
||||
<span class="text-green-400">●</span> SIAX Monitor
|
||||
Log Viewer
|
||||
</div>
|
||||
<div class="text-[#9dabb9] opacity-50">
|
||||
<span class="text-blue-400">ℹ</span> Select an
|
||||
application from the sidebar to view logs
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
const logs = document.querySelectorAll('.log-entry');
|
||||
logs.forEach(log => {
|
||||
const level = log.dataset.level;
|
||||
let show = false;
|
||||
<script>
|
||||
let ws = null;
|
||||
let autoScroll = true;
|
||||
let currentApp = null;
|
||||
|
||||
if (level === 'info' && showInfo) show = true;
|
||||
if (level === 'warning' && showWarning) show = true;
|
||||
if (level === 'error' && showError) show = true;
|
||||
if (level === 'critical' && showCritical) show = true;
|
||||
async function loadApps() {
|
||||
const appList = document.getElementById("app-list");
|
||||
const loading = document.getElementById("sidebar-loading");
|
||||
const empty = document.getElementById("sidebar-empty");
|
||||
|
||||
log.style.display = show ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
appList.classList.add("hidden");
|
||||
loading.classList.remove("hidden");
|
||||
empty.classList.add("hidden");
|
||||
|
||||
// Auto-refresh cada 30 segundos
|
||||
setTimeout(() => location.reload(), 30000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
try {
|
||||
const response = await fetch(
|
||||
"http://localhost:8080/api/apps",
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
loading.classList.add("hidden");
|
||||
|
||||
if (!data.apps || data.apps.length === 0) {
|
||||
empty.classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
appList.classList.remove("hidden");
|
||||
appList.innerHTML = data.apps
|
||||
.map((app) => {
|
||||
const statusColor =
|
||||
app.status === "Running"
|
||||
? "text-green-400"
|
||||
: app.status === "Stopped"
|
||||
? "text-gray-400"
|
||||
: "text-red-400";
|
||||
const statusIcon =
|
||||
app.status === "Running"
|
||||
? "play_circle"
|
||||
: app.status === "Stopped"
|
||||
? "stop_circle"
|
||||
: "error";
|
||||
|
||||
return `
|
||||
<button
|
||||
onclick="connectToApp('${app.name}')"
|
||||
class="app-item w-full text-left p-3 rounded-lg border border-[#283039] hover:border-primary hover:bg-[#1c2730] transition-all ${currentApp === app.name ? "bg-primary/20 border-primary" : ""}"
|
||||
data-app="${app.name}"
|
||||
>
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-white text-sm font-medium truncate">${app.name}</span>
|
||||
<span class="material-symbols-outlined ${statusColor} text-[16px]">${statusIcon}</span>
|
||||
</div>
|
||||
<div class="text-[#9dabb9] text-xs">${app.status}</div>
|
||||
</button>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
} catch (error) {
|
||||
console.error("Error loading apps:", error);
|
||||
loading.classList.add("hidden");
|
||||
empty.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function connectToApp(appName) {
|
||||
// Close existing connection
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
|
||||
currentApp = appName;
|
||||
document.getElementById("current-app-name").textContent =
|
||||
appName;
|
||||
document.getElementById("connection-status").textContent =
|
||||
"Connecting...";
|
||||
|
||||
// Clear log container
|
||||
const logContainer = document.getElementById("log-container");
|
||||
logContainer.innerHTML = `
|
||||
<div class="text-[#9dabb9]">
|
||||
<span class="text-blue-400">ℹ</span> Connecting to ${appName} logs...
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Update active state
|
||||
document.querySelectorAll(".app-item").forEach((item) => {
|
||||
if (item.dataset.app === appName) {
|
||||
item.classList.add("bg-primary/20", "border-primary");
|
||||
} else {
|
||||
item.classList.remove(
|
||||
"bg-primary/20",
|
||||
"border-primary",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Connect WebSocket
|
||||
ws = new WebSocket(
|
||||
`ws://localhost:8080/api/apps/${appName}/logs`,
|
||||
);
|
||||
|
||||
ws.onopen = () => {
|
||||
document.getElementById("connection-status").textContent =
|
||||
"Connected - Live streaming";
|
||||
appendLog("success", `Connected to ${appName} logs`);
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
try {
|
||||
const log = JSON.parse(event.data);
|
||||
appendLog("log", log.MESSAGE || event.data, log);
|
||||
} catch (e) {
|
||||
appendLog("log", event.data);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
appendLog("error", "WebSocket error occurred");
|
||||
document.getElementById("connection-status").textContent =
|
||||
"Connection error";
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
appendLog("warning", `Disconnected from ${appName}`);
|
||||
document.getElementById("connection-status").textContent =
|
||||
"Disconnected";
|
||||
};
|
||||
}
|
||||
|
||||
function appendLog(type, message, logData = null) {
|
||||
const logContainer = document.getElementById("log-container");
|
||||
const logEntry = document.createElement("div");
|
||||
logEntry.className = "log-line";
|
||||
|
||||
const timestamp = new Date()
|
||||
.toISOString()
|
||||
.split("T")[1]
|
||||
.slice(0, 12);
|
||||
|
||||
let icon = "●";
|
||||
let color = "text-white";
|
||||
|
||||
if (type === "error") {
|
||||
icon = "✖";
|
||||
color = "text-red-400";
|
||||
} else if (type === "warning") {
|
||||
icon = "⚠";
|
||||
color = "text-yellow-400";
|
||||
} else if (type === "success") {
|
||||
icon = "✓";
|
||||
color = "text-green-400";
|
||||
} else if (logData) {
|
||||
// Parse log priority
|
||||
const priority = logData.PRIORITY || "6";
|
||||
if (priority <= "3") {
|
||||
icon = "✖";
|
||||
color = "text-red-400";
|
||||
} else if (priority === "4") {
|
||||
icon = "⚠";
|
||||
color = "text-yellow-400";
|
||||
} else {
|
||||
icon = "●";
|
||||
color = "text-blue-400";
|
||||
}
|
||||
}
|
||||
|
||||
logEntry.innerHTML = `
|
||||
<span class="text-[#9dabb9] opacity-50">[${timestamp}]</span>
|
||||
<span class="${color}">${icon}</span>
|
||||
<span class="${color}">${escapeHtml(message)}</span>
|
||||
`;
|
||||
|
||||
logContainer.appendChild(logEntry);
|
||||
|
||||
// Auto-scroll
|
||||
if (autoScroll) {
|
||||
const terminal = document.getElementById("log-terminal");
|
||||
terminal.scrollTop = terminal.scrollHeight;
|
||||
}
|
||||
|
||||
// Limit to last 1000 lines
|
||||
while (logContainer.children.length > 1000) {
|
||||
logContainer.removeChild(logContainer.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function toggleAutoScroll() {
|
||||
autoScroll = !autoScroll;
|
||||
const btn = document.getElementById("auto-scroll-btn");
|
||||
btn.innerHTML = `
|
||||
<span class="material-symbols-outlined text-[18px]">swap_vert</span>
|
||||
Auto-scroll: ${autoScroll ? "ON" : "OFF"}
|
||||
`;
|
||||
if (autoScroll) {
|
||||
btn.classList.add(
|
||||
"bg-primary/20",
|
||||
"border-primary/30",
|
||||
"text-primary",
|
||||
);
|
||||
btn.classList.remove(
|
||||
"bg-[#1c2730]",
|
||||
"border-[#283039]",
|
||||
"text-[#9dabb9]",
|
||||
);
|
||||
} else {
|
||||
btn.classList.remove(
|
||||
"bg-primary/20",
|
||||
"border-primary/30",
|
||||
"text-primary",
|
||||
);
|
||||
btn.classList.add(
|
||||
"bg-[#1c2730]",
|
||||
"border-[#283039]",
|
||||
"text-[#9dabb9]",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function clearLogViewer() {
|
||||
const logContainer = document.getElementById("log-container");
|
||||
logContainer.innerHTML = `
|
||||
<div class="text-[#9dabb9]">
|
||||
<span class="text-blue-400">ℹ</span> Log viewer cleared
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Load apps on page load
|
||||
document.addEventListener("DOMContentLoaded", loadApps);
|
||||
|
||||
// Cleanup on page unload
|
||||
window.addEventListener("beforeunload", () => {
|
||||
if (ws) {
|
||||
ws.close();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
553
web/register.html
Normal file
553
web/register.html
Normal file
@@ -0,0 +1,553 @@
|
||||
<!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>Registrar Aplicación - SIAX Monitor</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#137fec",
|
||||
"background-light": "#f6f7f8",
|
||||
"background-dark": "#101922",
|
||||
},
|
||||
fontFamily: { display: ["Inter", "sans-serif"] },
|
||||
borderRadius: {
|
||||
DEFAULT: "0.25rem",
|
||||
lg: "0.5rem",
|
||||
xl: "0.75rem",
|
||||
full: "9999px",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
"FILL" 0,
|
||||
"wght" 400,
|
||||
"GRAD" 0,
|
||||
"opsz" 24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
class="bg-background-light dark:bg-background-dark font-display text-white min-h-screen flex flex-col"
|
||||
>
|
||||
<!-- Sticky Top Navigation -->
|
||||
<header
|
||||
class="sticky top-0 z-50 w-full border-b border-solid border-[#283039] bg-background-dark/80 backdrop-blur-md px-4 md:px-10 py-3"
|
||||
>
|
||||
<div
|
||||
class="max-w-[1200px] mx-auto flex items-center justify-between whitespace-nowrap"
|
||||
>
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="flex items-center gap-4 text-white">
|
||||
<div
|
||||
class="size-8 bg-primary rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<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="/select"
|
||||
>Agregar Detectada</a
|
||||
>
|
||||
<a
|
||||
class="text-primary text-sm font-medium border-b-2 border-primary pb-1"
|
||||
href="/register"
|
||||
>Nueva App</a
|
||||
>
|
||||
<a
|
||||
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||
href="/logs"
|
||||
>Registros</a
|
||||
>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 max-w-[900px] mx-auto w-full px-4 py-8 space-y-6">
|
||||
<!-- Page Heading -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1
|
||||
class="text-white text-4xl font-black leading-tight tracking-[-0.033em]"
|
||||
>
|
||||
Register New Application
|
||||
</h1>
|
||||
<p class="text-[#9dabb9] text-base font-normal">
|
||||
Register a Node.js or Python application to manage with
|
||||
systemd.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert Messages -->
|
||||
<div
|
||||
id="alert-success"
|
||||
class="hidden rounded-xl p-4 bg-green-500/20 border border-green-500/30"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-green-400"
|
||||
>check_circle</span
|
||||
>
|
||||
<p
|
||||
class="text-green-400 text-sm font-medium"
|
||||
id="success-message"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="alert-error"
|
||||
class="hidden rounded-xl p-4 bg-red-500/20 border border-red-500/30"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="material-symbols-outlined text-red-400"
|
||||
>error</span
|
||||
>
|
||||
<p
|
||||
class="text-red-400 text-sm font-medium"
|
||||
id="error-message"
|
||||
></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Registration Form -->
|
||||
<form id="registerForm" class="space-y-6">
|
||||
<!-- Basic Information Card -->
|
||||
<div
|
||||
class="rounded-xl border border-[#283039] bg-[#161f2a] p-6 space-y-5"
|
||||
>
|
||||
<h3 class="text-white text-lg font-bold">
|
||||
Basic Information
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||
Application Name <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="app_name"
|
||||
name="app_name"
|
||||
required
|
||||
placeholder="mi-app"
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<p class="text-[#9dabb9] text-xs">
|
||||
Solo letras, números, guiones y guiones bajos
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
class="block text-[#9dabb9] text-sm font-medium"
|
||||
>
|
||||
Application Type
|
||||
<span class="text-red-400">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="app_type"
|
||||
name="app_type"
|
||||
required
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
>
|
||||
<option value="nodejs">Node.js</option>
|
||||
<option value="python">Python / FastAPI</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
class="block text-[#9dabb9] text-sm font-medium"
|
||||
>
|
||||
Restart Policy
|
||||
<span class="text-red-400">*</span>
|
||||
</label>
|
||||
<select
|
||||
id="restart_policy"
|
||||
name="restart_policy"
|
||||
required
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
>
|
||||
<option value="always">
|
||||
Always (Always restart)
|
||||
</option>
|
||||
<option value="on-failure">
|
||||
On-Failure (Only if fails)
|
||||
</option>
|
||||
<option value="no">No (No reiniciar)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
rows="2"
|
||||
placeholder="Descripción de la aplicación..."
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Paths & User Card -->
|
||||
<div
|
||||
class="rounded-xl border border-[#283039] bg-[#161f2a] p-6 space-y-5"
|
||||
>
|
||||
<h3 class="text-white text-lg font-bold">
|
||||
Rutas y Usuario
|
||||
</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||
Script Path <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="script_path"
|
||||
name="script_path"
|
||||
required
|
||||
placeholder="/opt/apps/mi-app/index.js"
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<p class="text-[#9dabb9] text-xs">
|
||||
Ruta completa al archivo principal (.js o .py)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||
Working Directory
|
||||
<span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="working_directory"
|
||||
name="working_directory"
|
||||
required
|
||||
placeholder="/opt/apps/mi-app"
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<p class="text-[#9dabb9] text-xs">
|
||||
Directorio desde el cual se ejecutará la aplicación
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||
System User <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="user"
|
||||
name="user"
|
||||
required
|
||||
placeholder="nodejs"
|
||||
value="nodejs"
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<p class="text-[#9dabb9] text-xs">
|
||||
Usuario bajo el cual se ejecutará el proceso
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Environment Variables Card -->
|
||||
<div
|
||||
class="rounded-xl border border-[#283039] bg-[#161f2a] p-6 space-y-5"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-white text-lg font-bold">
|
||||
Environment Variables
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
onclick="addEnvVar()"
|
||||
class="flex items-center gap-2 px-4 py-2 bg-[#1c2730] hover:bg-[#283039] border border-[#283039] rounded-lg text-white text-sm font-medium transition-colors"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]"
|
||||
>add</span
|
||||
>
|
||||
Add Variable
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="env-container" class="space-y-3">
|
||||
<div
|
||||
class="env-item grid grid-cols-1 md:grid-cols-[1fr_1fr_auto] gap-3"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="KEY"
|
||||
class="env-key bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="valor"
|
||||
class="env-value bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onclick="removeEnvVar(this)"
|
||||
class="flex items-center justify-center w-full md:w-auto px-4 py-2.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 transition-colors"
|
||||
>
|
||||
<span
|
||||
class="material-symbols-outlined text-[18px]"
|
||||
>delete</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div
|
||||
class="flex flex-col-reverse sm:flex-row gap-3 justify-between pt-4"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick="window.location.href = '/'"
|
||||
class="flex items-center justify-center rounded-lg h-12 px-6 bg-[#1c2730] hover:bg-[#283039] border border-[#283039] text-white text-sm font-bold transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex items-center justify-center rounded-lg h-12 px-6 bg-primary hover:brightness-110 text-white text-sm font-bold transition-all gap-2"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]"
|
||||
>check_circle</span
|
||||
>
|
||||
<span>Registrar Aplicación</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function addEnvVar() {
|
||||
const container = document.getElementById("env-container");
|
||||
const envItem = document.createElement("div");
|
||||
envItem.className =
|
||||
"env-item grid grid-cols-1 md:grid-cols-[1fr_1fr_auto] gap-3";
|
||||
envItem.innerHTML = `
|
||||
<input
|
||||
type="text"
|
||||
placeholder="KEY"
|
||||
class="env-key bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="valor"
|
||||
class="env-value bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onclick="removeEnvVar(this)"
|
||||
class="flex items-center justify-center w-full md:w-auto px-4 py-2.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 transition-colors"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]">delete</span>
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(envItem);
|
||||
}
|
||||
|
||||
function removeEnvVar(btn) {
|
||||
const container = document.getElementById("env-container");
|
||||
if (container.children.length > 1) {
|
||||
btn.closest(".env-item").remove();
|
||||
}
|
||||
}
|
||||
|
||||
function showAlert(message, type) {
|
||||
const successAlert = document.getElementById("alert-success");
|
||||
const errorAlert = document.getElementById("alert-error");
|
||||
|
||||
if (type === "success") {
|
||||
document.getElementById("success-message").textContent =
|
||||
message;
|
||||
successAlert.classList.remove("hidden");
|
||||
errorAlert.classList.add("hidden");
|
||||
} else {
|
||||
document.getElementById("error-message").textContent =
|
||||
message;
|
||||
errorAlert.classList.remove("hidden");
|
||||
successAlert.classList.add("hidden");
|
||||
}
|
||||
|
||||
// Scroll to top
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
|
||||
// Hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
successAlert.classList.add("hidden");
|
||||
errorAlert.classList.add("hidden");
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("registerForm")
|
||||
.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = {
|
||||
app_name: document.getElementById("app_name").value,
|
||||
script_path:
|
||||
document.getElementById("script_path").value,
|
||||
working_directory:
|
||||
document.getElementById("working_directory").value,
|
||||
user: document.getElementById("user").value,
|
||||
environment: {},
|
||||
restart_policy:
|
||||
document.getElementById("restart_policy").value,
|
||||
app_type: document.getElementById("app_type").value,
|
||||
description:
|
||||
document.getElementById("description").value ||
|
||||
null,
|
||||
};
|
||||
|
||||
// Collect environment variables
|
||||
const envItems = document.querySelectorAll(".env-item");
|
||||
envItems.forEach((item) => {
|
||||
const key = item.querySelector(".env-key").value;
|
||||
const value = item.querySelector(".env-value").value;
|
||||
if (key && value) {
|
||||
formData.environment[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"http://localhost:8080/api/apps",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(formData),
|
||||
},
|
||||
);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showAlert(
|
||||
`Application registered successfully: ${formData.app_name}`,
|
||||
"success",
|
||||
);
|
||||
|
||||
// Ask if user wants to start the app
|
||||
if (
|
||||
confirm("¿Deseas iniciar la aplicación ahora?")
|
||||
) {
|
||||
const startResponse = await fetch(
|
||||
`http://localhost:8080/api/apps/${formData.app_name}/start`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
|
||||
const startResult = await startResponse.json();
|
||||
if (startResult.success) {
|
||||
showAlert(
|
||||
"Application started successfully!",
|
||||
"success",
|
||||
);
|
||||
setTimeout(() => {
|
||||
window.location.href = "/";
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
window.location.href = "/";
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Reset form
|
||||
document.getElementById("registerForm").reset();
|
||||
document.getElementById("env-container").innerHTML =
|
||||
`
|
||||
<div class="env-item grid grid-cols-1 md:grid-cols-[1fr_1fr_auto] gap-3">
|
||||
<input type="text" placeholder="KEY" class="env-key bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" />
|
||||
<input type="text" placeholder="valor" class="env-value bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" />
|
||||
<button type="button" onclick="removeEnvVar(this)" class="flex items-center justify-center w-full md:w-auto px-4 py-2.5 bg-red-500/20 hover:bg-red-500/30 border border-red-500/30 rounded-lg text-red-400 transition-colors">
|
||||
<span class="material-symbols-outlined text-[18px]">delete</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
showAlert(
|
||||
"Error: " +
|
||||
(result.error || "Error desconocido"),
|
||||
"error",
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert(
|
||||
"Connection error: " + error.message,
|
||||
"error",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-fill working_directory based on script_path
|
||||
document
|
||||
.getElementById("script_path")
|
||||
.addEventListener("blur", function () {
|
||||
const scriptPath = this.value;
|
||||
const workingDirInput =
|
||||
document.getElementById("working_directory");
|
||||
|
||||
if (scriptPath && !workingDirInput.value) {
|
||||
const dir = scriptPath.substring(
|
||||
0,
|
||||
scriptPath.lastIndexOf("/"),
|
||||
);
|
||||
workingDirInput.value = dir;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
420
web/scan.html
420
web/scan.html
@@ -1,71 +1,349 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Scan Results - SIAX</title>
|
||||
<style>
|
||||
body {
|
||||
background: #0f172a;
|
||||
color: white;
|
||||
font-family: monospace;
|
||||
padding: 40px;
|
||||
}
|
||||
h1 {
|
||||
color: #3b82f6;
|
||||
}
|
||||
.process {
|
||||
background: #1e293b;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
.pid {
|
||||
color: #22c55e;
|
||||
font-weight: bold;
|
||||
}
|
||||
.name {
|
||||
color: #60a5fa;
|
||||
}
|
||||
.cpu {
|
||||
color: #f59e0b;
|
||||
}
|
||||
.mem {
|
||||
color: #8b5cf6;
|
||||
}
|
||||
.path {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.summary {
|
||||
color: #22c55e;
|
||||
font-size: 18px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.no-results {
|
||||
background: #7f1d1d;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #ef4444;
|
||||
}
|
||||
.back-btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background: #64748b;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.back-btn:hover {
|
||||
background: #475569;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔍 Escaneo de Procesos Node.js</h1>
|
||||
{{CONTENT}}
|
||||
<a href="/" class="back-btn">← Volver al Panel</a>
|
||||
</body>
|
||||
</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>Escaneo de Procesos - SIAX Monitor</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#137fec",
|
||||
"background-light": "#f6f7f8",
|
||||
"background-dark": "#101922",
|
||||
},
|
||||
fontFamily: { display: ["Inter", "sans-serif"] },
|
||||
borderRadius: {
|
||||
DEFAULT: "0.25rem",
|
||||
lg: "0.5rem",
|
||||
xl: "0.75rem",
|
||||
full: "9999px",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
"FILL" 0,
|
||||
"wght" 400,
|
||||
"GRAD" 0,
|
||||
"opsz" 24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
class="bg-background-light dark:bg-background-dark font-display text-white min-h-screen flex flex-col"
|
||||
>
|
||||
<!-- Sticky Top Navigation -->
|
||||
<header
|
||||
class="sticky top-0 z-50 w-full border-b border-solid border-[#283039] bg-background-dark/80 backdrop-blur-md px-4 md:px-10 py-3"
|
||||
>
|
||||
<div
|
||||
class="max-w-[1200px] mx-auto flex items-center justify-between whitespace-nowrap"
|
||||
>
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="flex items-center gap-4 text-white">
|
||||
<div
|
||||
class="size-8 bg-primary rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<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-primary text-sm font-medium border-b-2 border-primary pb-1"
|
||||
href="/scan"
|
||||
>Escanear</a
|
||||
>
|
||||
<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
|
||||
>
|
||||
<a
|
||||
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||
href="/logs"
|
||||
>Registros</a
|
||||
>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 max-w-[1200px] mx-auto w-full px-4 py-8 space-y-6">
|
||||
<!-- Page Heading -->
|
||||
<div class="flex flex-wrap justify-between items-end gap-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h1
|
||||
class="text-white text-4xl font-black leading-tight tracking-[-0.033em]"
|
||||
>
|
||||
Process Scan View
|
||||
</h1>
|
||||
<p class="text-[#9dabb9] text-base font-normal">
|
||||
Monitoreo activo de procesos Node.js y Python.
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
class="flex items-center justify-center rounded-lg h-10 px-4 bg-primary text-white text-sm font-bold hover:brightness-110 transition-all gap-2"
|
||||
onclick="refreshScan()"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]"
|
||||
>refresh</span
|
||||
>
|
||||
<span>Actualizar Escaneo</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats Overview -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div
|
||||
class="flex flex-col gap-2 rounded-xl p-6 border border-[#283039] bg-[#161f2a]"
|
||||
>
|
||||
<div class="flex justify-between items-start">
|
||||
<p class="text-[#9dabb9] text-sm font-medium">
|
||||
Total Processes
|
||||
</p>
|
||||
<span class="material-symbols-outlined text-primary"
|
||||
>analytics</span
|
||||
>
|
||||
</div>
|
||||
<p
|
||||
class="text-white text-3xl font-bold"
|
||||
id="total-processes"
|
||||
>
|
||||
0
|
||||
</p>
|
||||
<p class="text-[#9dabb9] text-sm font-medium">
|
||||
Detectados en este escaneo
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-2 rounded-xl p-6 border border-[#283039] bg-[#161f2a]"
|
||||
>
|
||||
<div class="flex justify-between items-start">
|
||||
<p class="text-[#9dabb9] text-sm font-medium">
|
||||
Node.js Processes
|
||||
</p>
|
||||
<span class="material-symbols-outlined text-green-400"
|
||||
>terminal</span
|
||||
>
|
||||
</div>
|
||||
<p class="text-white text-3xl font-bold" id="node-count">
|
||||
0
|
||||
</p>
|
||||
<p class="text-[#9dabb9] text-sm font-medium">
|
||||
Running instances
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col gap-2 rounded-xl p-6 border border-[#283039] bg-[#161f2a]"
|
||||
>
|
||||
<div class="flex justify-between items-start">
|
||||
<p class="text-[#9dabb9] text-sm font-medium">
|
||||
Python Processes
|
||||
</p>
|
||||
<span class="material-symbols-outlined text-blue-400"
|
||||
>code</span
|
||||
>
|
||||
</div>
|
||||
<p class="text-white text-3xl font-bold" id="python-count">
|
||||
0
|
||||
</p>
|
||||
<p class="text-[#9dabb9] text-sm font-medium">
|
||||
Running instances
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Process Table -->
|
||||
<div
|
||||
class="rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between p-4 border-b border-[#283039]"
|
||||
>
|
||||
<h3 class="text-white font-bold px-2">
|
||||
Detected Processes
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-[#1c2730] border-b border-[#283039]">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left text-[#9dabb9] text-sm font-medium">PID</th>
|
||||
<th class="px-4 py-3 text-left text-[#9dabb9] text-sm font-medium">Process Name</th>
|
||||
<th class="px-4 py-3 text-left text-[#9dabb9] text-sm font-medium">Usuario</th>
|
||||
<th class="px-4 py-3 text-left text-[#9dabb9] text-sm font-medium">CPU %</th>
|
||||
<th class="px-4 py-3 text-left text-[#9dabb9] text-sm font-medium">Memoria</th>
|
||||
<th class="px-4 py-3 text-left text-[#9dabb9] text-sm font-medium">Status</th>
|
||||
<th class="px-4 py-3 text-left text-[#9dabb9] text-sm font-medium">Tipo</th>
|
||||
<th class="px-4 py-3 text-left text-[#9dabb9] text-sm font-medium">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="processes-tbody" class="divide-y divide-[#283039]">
|
||||
<!-- Rows will be populated by JavaScript -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div id="empty-state" class="hidden p-8 text-center">
|
||||
<span class="material-symbols-outlined text-[#9dabb9] text-5xl mb-3">search_off</span>
|
||||
<p class="text-[#9dabb9] text-sm">No se detectaron procesos. Haz clic en "Actualizar Escaneo" para intentar de nuevo.</p>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div id="loading-state" class="p-8 text-center">
|
||||
<span class="material-symbols-outlined text-primary text-5xl mb-3 animate-spin">progress_activity</span>
|
||||
<p class="text-[#9dabb9] text-sm">Escaneando procesos...</p>
|
||||
</div>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
async function loadProcesses() {
|
||||
const tbody = document.getElementById('processes-tbody');
|
||||
const emptyState = document.getElementById('empty-state');
|
||||
const loadingState = document.getElementById('loading-state');
|
||||
const totalProcesses = document.getElementById('total-processes');
|
||||
const nodeCount = document.getElementById('node-count');
|
||||
const pythonCount = document.getElementById('python-count');
|
||||
|
||||
try {
|
||||
loadingState.classList.remove('hidden');
|
||||
emptyState.classList.add('hidden');
|
||||
|
||||
const response = await fetch('http://localhost:8080/api/scan');
|
||||
if (!response.ok) throw new Error('Failed to fetch processes');
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
loadingState.classList.add('hidden');
|
||||
|
||||
if (!data.data || !data.data.processes || data.data.processes.length === 0) {
|
||||
emptyState.classList.remove('hidden');
|
||||
totalProcesses.textContent = '0';
|
||||
nodeCount.textContent = '0';
|
||||
pythonCount.textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
// Update stats
|
||||
const nodeProcesses = data.data.processes.filter(p => p.process_type === 'nodejs');
|
||||
const pythonProcesses = data.data.processes.filter(p => p.process_type === 'python');
|
||||
|
||||
totalProcesses.textContent = data.data.processes.length;
|
||||
nodeCount.textContent = nodeProcesses.length;
|
||||
pythonCount.textContent = pythonProcesses.length;
|
||||
|
||||
// Populate table
|
||||
tbody.innerHTML = data.data.processes.map(process => {
|
||||
const typeIcon = process.process_type === 'nodejs' ? 'terminal' : 'code';
|
||||
const typeColor = process.process_type === 'nodejs' ? 'text-green-400' : 'text-blue-400';
|
||||
const typeLabel = process.process_type === 'nodejs' ? 'Node.js' : 'Python';
|
||||
|
||||
return `
|
||||
<tr class="hover:bg-[#1c2730] transition-colors">
|
||||
<td class="px-4 py-3 text-white text-sm font-mono">${process.pid}</td>
|
||||
<td class="px-4 py-3 text-white text-sm font-medium">${process.name}</td>
|
||||
<td class="px-4 py-3 text-[#9dabb9] text-sm">${process.user || 'N/A'}</td>
|
||||
<td class="px-4 py-3 text-white text-sm">${process.cpu_usage?.toFixed(2) || '0.00'}%</td>
|
||||
<td class="px-4 py-3 text-white text-sm">${formatMemory(process.memory_mb)}</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-green-500/20 text-green-400">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-green-400"></span>
|
||||
Running
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span class="inline-flex items-center gap-1 ${typeColor}">
|
||||
<span class="material-symbols-outlined text-[18px]">${typeIcon}</span>
|
||||
<span class="text-sm font-medium">${typeLabel}</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<button
|
||||
onclick="viewDetails(${process.pid})"
|
||||
class="text-primary hover:text-white text-sm font-medium transition-colors"
|
||||
>
|
||||
Details
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading processes:', error);
|
||||
loadingState.classList.add('hidden');
|
||||
emptyState.classList.remove('hidden');
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="8" class="px-4 py-8 text-center">
|
||||
<span class="material-symbols-outlined text-red-400 text-5xl mb-3">error</span>
|
||||
<p class="text-red-400 text-sm">Error al cargar procesos. Asegúrate de que la API esté ejecutándose en el puerto 8080.</p>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function formatMemory(mb) {
|
||||
if (!mb) return '0 MB';
|
||||
if (mb < 1024) return `${mb.toFixed(0)} MB`;
|
||||
return `${(mb / 1024).toFixed(2)} GB`;
|
||||
}
|
||||
|
||||
function viewDetails(pid) {
|
||||
alert(`Detalles del proceso para PID ${pid} - ¡Función próximamente!`);
|
||||
}
|
||||
|
||||
function refreshScan() {
|
||||
loadProcesses();
|
||||
}
|
||||
|
||||
// Load processes on page load
|
||||
document.addEventListener('DOMContentLoaded', loadProcesses);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
564
web/select.html
564
web/select.html
@@ -1,160 +1,416 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Gestionar Procesos - SIAX</title>
|
||||
<style>
|
||||
body {
|
||||
background: #0f172a;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
padding: 40px;
|
||||
}
|
||||
h1 {
|
||||
color: #3b82f6;
|
||||
}
|
||||
h2 {
|
||||
color: #60a5fa;
|
||||
margin-top: 40px;
|
||||
}
|
||||
.process-item {
|
||||
background: #1e293b;
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.process-info {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.pid {
|
||||
color: #22c55e;
|
||||
font-weight: bold;
|
||||
}
|
||||
.path {
|
||||
color: #94a3b8;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.select-btn {
|
||||
padding: 8px 16px;
|
||||
background: #22c55e;
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.select-btn:hover {
|
||||
background: #16a34a;
|
||||
}
|
||||
.form-section {
|
||||
background: #1e293b;
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
margin-top: 20px;
|
||||
border: 2px solid #3b82f6;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
color: #60a5fa;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: #0f172a;
|
||||
border: 2px solid #475569;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
small {
|
||||
color: #94a3b8;
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.submit-btn {
|
||||
padding: 12px 24px;
|
||||
background: #3b82f6;
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.submit-btn:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
.back-btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background: #64748b;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.back-btn:hover {
|
||||
background: #475569;
|
||||
}
|
||||
.no-results {
|
||||
background: #7f1d1d;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #ef4444;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>⚙️ Gestionar Procesos a Monitorear</h1>
|
||||
<!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>Agregar App Detectada - SIAX Monitor</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#137fec",
|
||||
"background-light": "#f6f7f8",
|
||||
"background-dark": "#101922",
|
||||
},
|
||||
fontFamily: { display: ["Inter", "sans-serif"] },
|
||||
borderRadius: {
|
||||
DEFAULT: "0.25rem",
|
||||
lg: "0.5rem",
|
||||
xl: "0.75rem",
|
||||
full: "9999px",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
"FILL" 0,
|
||||
"wght" 400,
|
||||
"GRAD" 0,
|
||||
"opsz" 24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
class="bg-background-light dark:bg-background-dark font-display text-white min-h-screen flex flex-col"
|
||||
>
|
||||
<!-- Sticky Top Navigation -->
|
||||
<header
|
||||
class="sticky top-0 z-50 w-full border-b border-solid border-[#283039] bg-background-dark/80 backdrop-blur-md px-4 md:px-10 py-3"
|
||||
>
|
||||
<div
|
||||
class="max-w-[1200px] mx-auto flex items-center justify-between whitespace-nowrap"
|
||||
>
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="flex items-center gap-4 text-white">
|
||||
<div
|
||||
class="size-8 bg-primary rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<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-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
|
||||
>
|
||||
<a
|
||||
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||
href="/logs"
|
||||
>Registros</a
|
||||
>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<h2>📋 Procesos Node.js Detectados</h2>
|
||||
{{PROCESSES_LIST}}
|
||||
<main class="flex-1 max-w-[1200px] mx-auto w-full px-4 py-8 space-y-6">
|
||||
<!-- Page Heading -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<h1
|
||||
class="text-white text-4xl font-black leading-tight tracking-[-0.033em]"
|
||||
>
|
||||
Add Detected Application
|
||||
</h1>
|
||||
<p class="text-[#9dabb9] text-base font-normal">
|
||||
Selecciona un proceso detectado y configúralo para
|
||||
monitoreo.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>➕ Agregar Proceso Personalizado</h2>
|
||||
<div class="form-section">
|
||||
<form method="POST" action="/add-process">
|
||||
<div class="form-group">
|
||||
<label for="app_name">Nombre de la Aplicación:</label>
|
||||
<input type="text" id="app_name" name="app_name" placeholder="Ej: app_tareas, fidelizacion, mi-api" required>
|
||||
<small>💡 Este nombre se usará para identificar el proceso en el directorio de trabajo</small>
|
||||
</div>
|
||||
<!-- Detected Processes Section -->
|
||||
<div
|
||||
class="rounded-xl border border-[#283039] bg-[#161f2a] overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between p-4 border-b border-[#283039]"
|
||||
>
|
||||
<h3 class="text-white font-bold px-2">
|
||||
Detected Node.js & Python Processes
|
||||
</h3>
|
||||
<button
|
||||
onclick="window.location.href = '/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-[18px]"
|
||||
>refresh</span
|
||||
>
|
||||
Scan Again
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="port">Puerto:</label>
|
||||
<input type="number" id="port" name="port" placeholder="Ej: 3000, 3001, 8080" required>
|
||||
<small>💡 Puerto donde corre la aplicación</small>
|
||||
</div>
|
||||
<div id="processes-container" class="p-4">
|
||||
<!-- Processes will be loaded here -->
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-btn">💾 Guardar y Monitorear</button>
|
||||
</form>
|
||||
</div>
|
||||
<!-- Loading State -->
|
||||
<div id="loading-state" class="p-8 text-center">
|
||||
<span
|
||||
class="material-symbols-outlined text-primary text-5xl mb-3 animate-spin"
|
||||
>progress_activity</span
|
||||
>
|
||||
<p class="text-[#9dabb9] text-sm">
|
||||
Cargando procesos detectados...
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a href="/" class="back-btn">← Volver al Panel</a>
|
||||
<!-- Empty State -->
|
||||
<div id="empty-state" class="hidden p-8 text-center">
|
||||
<span
|
||||
class="material-symbols-outlined text-[#9dabb9] text-5xl mb-3"
|
||||
>search_off</span
|
||||
>
|
||||
<p class="text-[#9dabb9] text-sm mb-4">
|
||||
No se detectaron procesos
|
||||
</p>
|
||||
<button
|
||||
onclick="window.location.href = '/scan'"
|
||||
class="px-4 py-2 bg-primary hover:brightness-110 rounded-lg text-white text-sm font-medium transition-all"
|
||||
>
|
||||
Run Scan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function fillForm(appName, pid) {
|
||||
document.getElementById('app_name').value = appName;
|
||||
document.querySelector('.form-section').scrollIntoView({ behavior: 'smooth' });
|
||||
document.querySelector('.form-section').style.borderColor = '#22c55e';
|
||||
setTimeout(() => {
|
||||
document.querySelector('.form-section').style.borderColor = '#3b82f6';
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!-- Quick Add Form -->
|
||||
<div
|
||||
class="rounded-xl border border-[#283039] bg-[#161f2a] p-6 space-y-5"
|
||||
>
|
||||
<h3 class="text-white text-lg font-bold">
|
||||
Quick Configuration
|
||||
</h3>
|
||||
<p class="text-[#9dabb9] text-sm">
|
||||
Click on a detected process above, or fill in the details
|
||||
manually to add it to monitoring.
|
||||
</p>
|
||||
|
||||
<form id="quickAddForm" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
class="block text-[#9dabb9] text-sm font-medium"
|
||||
>
|
||||
Application Name
|
||||
<span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="app_name"
|
||||
name="app_name"
|
||||
required
|
||||
placeholder="mi-app"
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label
|
||||
class="block text-[#9dabb9] text-sm font-medium"
|
||||
>
|
||||
Process ID (PID)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="pid"
|
||||
name="pid"
|
||||
readonly
|
||||
placeholder="Auto-detectado"
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-[#9dabb9] placeholder-[#9dabb9] cursor-not-allowed"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="block text-[#9dabb9] text-sm font-medium">
|
||||
Script Path <span class="text-red-400">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="script_path"
|
||||
name="script_path"
|
||||
required
|
||||
placeholder="/opt/apps/mi-app/index.js"
|
||||
class="w-full bg-[#1c2730] border border-[#283039] rounded-lg px-4 py-2.5 text-white placeholder-[#9dabb9] focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col-reverse sm:flex-row gap-3 justify-end pt-4"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick="resetForm()"
|
||||
class="flex items-center justify-center rounded-lg h-12 px-6 bg-[#1c2730] hover:bg-[#283039] border border-[#283039] text-white text-sm font-bold transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick="window.location.href = '/register'"
|
||||
class="flex items-center justify-center rounded-lg h-12 px-6 bg-[#283039] hover:bg-[#3a4654] border border-[#283039] text-white text-sm font-bold transition-colors gap-2"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]"
|
||||
>settings</span
|
||||
>
|
||||
<span>Configuración Avanzada</span>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="flex items-center justify-center rounded-lg h-12 px-6 bg-primary hover:brightness-110 text-white text-sm font-bold transition-all gap-2"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]"
|
||||
>add_circle</span
|
||||
>
|
||||
<span>Agregar al Monitor</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
let detectedProcesses = [];
|
||||
|
||||
async function loadProcesses() {
|
||||
const container = document.getElementById(
|
||||
"processes-container",
|
||||
);
|
||||
const loading = document.getElementById("loading-state");
|
||||
const empty = document.getElementById("empty-state");
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
"http://localhost:8080/api/scan",
|
||||
);
|
||||
if (!response.ok)
|
||||
throw new Error("Failed to fetch processes");
|
||||
|
||||
const data = await response.json();
|
||||
detectedProcesses = data.data.processes || [];
|
||||
|
||||
loading.classList.add("hidden");
|
||||
|
||||
if (detectedProcesses.length === 0) {
|
||||
empty.classList.remove("hidden");
|
||||
container.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
container.classList.remove("hidden");
|
||||
container.innerHTML = `
|
||||
<div class="space-y-3">
|
||||
${detectedProcesses
|
||||
.map((process) => {
|
||||
const typeIcon =
|
||||
process.process_type === "nodejs"
|
||||
? "terminal"
|
||||
: "code";
|
||||
const typeColor =
|
||||
process.process_type === "nodejs"
|
||||
? "text-green-400"
|
||||
: "text-blue-400";
|
||||
const typeLabel =
|
||||
process.process_type === "nodejs"
|
||||
? "Node.js"
|
||||
: "Python";
|
||||
|
||||
return `
|
||||
<div class="flex items-center justify-between p-4 rounded-lg border border-[#283039] hover:border-primary hover:bg-[#1c2730] transition-all cursor-pointer" onclick="selectProcess(${process.pid})">
|
||||
<div class="flex items-center gap-4 flex-1">
|
||||
<div class="flex items-center justify-center w-12 h-12 rounded-lg bg-[#1c2730]">
|
||||
<span class="material-symbols-outlined ${typeColor}">${typeIcon}</span>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-3 mb-1">
|
||||
<span class="text-white font-medium">${process.name}</span>
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium ${typeColor} bg-current bg-opacity-20">
|
||||
${typeLabel}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-[#9dabb9] text-sm">
|
||||
PID: <span class="font-mono">${process.pid}</span> ·
|
||||
CPU: ${process.cpu_usage?.toFixed(2) || "0.00"}% ·
|
||||
Memory: ${formatMemory(process.memory_mb)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onclick="event.stopPropagation(); selectProcess(${process.pid})"
|
||||
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-[18px]">arrow_forward</span>
|
||||
Select
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join("")}
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error("Error loading processes:", error);
|
||||
loading.classList.add("hidden");
|
||||
empty.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function selectProcess(pid) {
|
||||
const process = detectedProcesses.find((p) => p.pid === pid);
|
||||
if (!process) return;
|
||||
|
||||
document.getElementById("app_name").value = process.name || "";
|
||||
document.getElementById("pid").value = pid;
|
||||
document.getElementById("script_path").value = ""; // User needs to provide this
|
||||
|
||||
// Scroll to form
|
||||
document
|
||||
.getElementById("quickAddForm")
|
||||
.scrollIntoView({ behavior: "smooth" });
|
||||
|
||||
// Focus on script path
|
||||
setTimeout(() => {
|
||||
document.getElementById("script_path").focus();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function formatMemory(mb) {
|
||||
if (!mb) return "0 MB";
|
||||
if (mb < 1024) return `${mb.toFixed(0)} MB`;
|
||||
return `${(mb / 1024).toFixed(2)} GB`;
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
document.getElementById("quickAddForm").reset();
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById("quickAddForm")
|
||||
.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
alert(
|
||||
'¡Función de agregar rápido próximamente! Por favor usa el botón "Configuración Avanzada" para registrar la aplicación con configuración completa.',
|
||||
);
|
||||
|
||||
// Redirect to register page with pre-filled data
|
||||
const appName = document.getElementById("app_name").value;
|
||||
const scriptPath =
|
||||
document.getElementById("script_path").value;
|
||||
|
||||
if (appName && scriptPath) {
|
||||
// Store in sessionStorage for pre-filling
|
||||
sessionStorage.setItem("prefill_app_name", appName);
|
||||
sessionStorage.setItem(
|
||||
"prefill_script_path",
|
||||
scriptPath,
|
||||
);
|
||||
window.location.href = "/register";
|
||||
}
|
||||
});
|
||||
|
||||
// Load processes on page load
|
||||
document.addEventListener("DOMContentLoaded", loadProcesses);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
295
web/success.html
295
web/success.html
@@ -1,58 +1,241 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="3;url=/select">
|
||||
<title>Proceso Agregado - SIAX</title>
|
||||
<style>
|
||||
body {
|
||||
background: #0f172a;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.success {
|
||||
background: #064e3b;
|
||||
border: 2px solid #22c55e;
|
||||
padding: 40px;
|
||||
border-radius: 16px;
|
||||
max-width: 500px;
|
||||
margin: 100px auto;
|
||||
}
|
||||
h1 {
|
||||
color: #22c55e;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.details {
|
||||
background: #0f172a;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
text-align: left;
|
||||
}
|
||||
.label {
|
||||
color: #60a5fa;
|
||||
font-weight: bold;
|
||||
}
|
||||
.redirect-msg {
|
||||
color: #94a3b8;
|
||||
margin-top: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="success">
|
||||
<h1>✅ Proceso Agregado Exitosamente</h1>
|
||||
<p style="color:#94a3b8;">El proceso será monitoreado en el próximo ciclo</p>
|
||||
<!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>Éxito - SIAX Monitor</title>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: "class",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: "#137fec",
|
||||
"background-light": "#f6f7f8",
|
||||
"background-dark": "#101922",
|
||||
},
|
||||
fontFamily: { display: ["Inter", "sans-serif"] },
|
||||
borderRadius: {
|
||||
DEFAULT: "0.25rem",
|
||||
lg: "0.5rem",
|
||||
xl: "0.75rem",
|
||||
full: "9999px",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Inter", sans-serif;
|
||||
}
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings:
|
||||
"FILL" 0,
|
||||
"wght" 400,
|
||||
"GRAD" 0,
|
||||
"opsz" 24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
class="bg-background-light dark:bg-background-dark font-display text-white min-h-screen flex flex-col"
|
||||
>
|
||||
<!-- Sticky Top Navigation -->
|
||||
<header
|
||||
class="sticky top-0 z-50 w-full border-b border-solid border-[#283039] bg-background-dark/80 backdrop-blur-md px-4 md:px-10 py-3"
|
||||
>
|
||||
<div
|
||||
class="max-w-[1200px] mx-auto flex items-center justify-between whitespace-nowrap"
|
||||
>
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="flex items-center gap-4 text-white">
|
||||
<div
|
||||
class="size-8 bg-primary rounded-lg flex items-center justify-center"
|
||||
>
|
||||
<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="/select"
|
||||
>
|
||||
Agregar Detectada
|
||||
</a>
|
||||
<a
|
||||
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||
href="/register"
|
||||
>
|
||||
Nueva App
|
||||
</a>
|
||||
<a
|
||||
class="text-[#9dabb9] text-sm font-medium hover:text-white transition-colors"
|
||||
href="/logs"
|
||||
>
|
||||
Registros
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="details">
|
||||
<p><span class="label">Aplicación:</span> {{APP_NAME}}</p>
|
||||
<p><span class="label">Puerto:</span> {{PORT}}</p>
|
||||
</div>
|
||||
<main class="flex-1 flex items-center justify-center px-4 py-16">
|
||||
<div class="max-w-2xl w-full">
|
||||
<!-- Success Card -->
|
||||
<div
|
||||
class="rounded-xl border border-green-500/30 bg-green-500/10 p-8 text-center space-y-6"
|
||||
>
|
||||
<!-- Success Icon -->
|
||||
<div class="flex justify-center">
|
||||
<div
|
||||
class="flex items-center justify-center w-24 h-24 rounded-full bg-green-500/20 border-4 border-green-500/30"
|
||||
>
|
||||
<span
|
||||
class="material-symbols-outlined text-green-400"
|
||||
style="font-size: 60px"
|
||||
>check_circle</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="redirect-msg">Redirigiendo en 3 segundos...</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<!-- Success Message -->
|
||||
<div class="space-y-2">
|
||||
<h1
|
||||
class="text-white text-3xl font-black leading-tight tracking-[-0.033em]"
|
||||
>
|
||||
Operation Successful!
|
||||
</h1>
|
||||
<p class="text-green-400 text-lg font-medium">
|
||||
The process has been added to monitoring
|
||||
successfully
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Application Info -->
|
||||
<div
|
||||
class="rounded-xl border border-[#283039] bg-[#161f2a] p-6 space-y-4"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[#9dabb9] text-sm font-medium"
|
||||
>Nombre de Aplicación</span
|
||||
>
|
||||
<span class="text-white text-sm font-bold"
|
||||
>{{APP_NAME}}</span
|
||||
>
|
||||
</div>
|
||||
<div class="border-t border-[#283039]"></div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[#9dabb9] text-sm font-medium"
|
||||
>Puerto</span
|
||||
>
|
||||
<span class="text-white text-sm font-bold"
|
||||
>{{PORT}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Message -->
|
||||
<div
|
||||
class="rounded-xl border border-primary/30 bg-primary/10 p-4"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<span
|
||||
class="material-symbols-outlined text-primary mt-0.5"
|
||||
>info</span
|
||||
>
|
||||
<p class="text-[#9dabb9] text-sm text-left">
|
||||
The monitor will start reporting metrics in the
|
||||
next cycle (60 segundos...You can view the
|
||||
application status on the dashboard.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div
|
||||
class="flex flex-col sm:flex-row gap-3 justify-center pt-4"
|
||||
>
|
||||
<a
|
||||
href="/"
|
||||
class="flex items-center justify-center rounded-lg h-12 px-6 bg-primary hover:brightness-110 text-white text-sm font-bold transition-all gap-2"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]"
|
||||
>home</span
|
||||
>
|
||||
<span>Ir al Panel</span>
|
||||
</a>
|
||||
<a
|
||||
href="/select"
|
||||
class="flex items-center justify-center rounded-lg h-12 px-6 bg-[#1c2730] hover:bg-[#283039] border border-[#283039] text-white text-sm font-bold transition-colors gap-2"
|
||||
>
|
||||
<span class="material-symbols-outlined text-[18px]"
|
||||
>add_circle</span
|
||||
>
|
||||
<span>Agregar Otra</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto-redirect countdown -->
|
||||
<div class="mt-6 text-center">
|
||||
<p class="text-[#9dabb9] text-sm">
|
||||
Redirigiendo al panel en
|
||||
<span id="countdown" class="text-white font-bold"
|
||||
>5</span
|
||||
>
|
||||
segundos...
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Auto-redirect countdown
|
||||
let segundos...5;
|
||||
const countdownElement = document.getElementById("countdown");
|
||||
|
||||
const interval = setInterval(() => {
|
||||
segundos...
|
||||
if (countdownElement) {
|
||||
countdownElement.textContent = seconds;
|
||||
}
|
||||
|
||||
if (segundos... 0) {
|
||||
clearInterval(interval);
|
||||
window.location.href = "/";
|
||||
}
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user