- Complete Flask-based control panel for multi-tenant POS instances - Dashboard with global stats, system health, and recent demos - Demo provisioning in 1 click with auto-expiration tracking - Tenant management: activate/deactivate, reset data, delete - Health monitoring: PostgreSQL, Redis, disk, memory, systemd services - Migration orchestration UI for running schema updates across all tenants - JWT authentication with manager_users table - Dark theme SPA frontend with real-time search and actions - systemd service file included
325 lines
16 KiB
HTML
325 lines
16 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Nexus Instance Manager</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
|
<link rel="stylesheet" href="/static/css/manager.css">
|
|
</head>
|
|
<body>
|
|
<!-- Login Screen -->
|
|
<div id="login-screen" class="login-screen">
|
|
<div class="login-card">
|
|
<div class="login-logo">
|
|
<i class="fas fa-cube"></i>
|
|
<h1>Nexus Manager</h1>
|
|
<p>Control Central de Instancias</p>
|
|
</div>
|
|
<form id="login-form">
|
|
<div class="form-group">
|
|
<label>Email</label>
|
|
<input type="email" id="login-email" required placeholder="admin@nexus.local">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Contraseña</label>
|
|
<input type="password" id="login-password" required placeholder="••••••••">
|
|
</div>
|
|
<button type="submit" class="btn btn-primary btn-block">
|
|
<i class="fas fa-sign-in-alt"></i> Ingresar
|
|
</button>
|
|
<div id="login-error" class="alert alert-error" style="display:none;"></div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main App -->
|
|
<div id="app" class="app" style="display:none;">
|
|
<!-- Sidebar -->
|
|
<aside class="sidebar">
|
|
<div class="sidebar-brand">
|
|
<i class="fas fa-cube"></i>
|
|
<span>Nexus Manager</span>
|
|
</div>
|
|
<nav class="sidebar-nav">
|
|
<a href="#/dashboard" class="nav-item active" data-page="dashboard">
|
|
<i class="fas fa-chart-line"></i>
|
|
<span>Dashboard</span>
|
|
</a>
|
|
<a href="#/demos" class="nav-item" data-page="demos">
|
|
<i class="fas fa-rocket"></i>
|
|
<span>Crear Demos</span>
|
|
</a>
|
|
<a href="#/tenants" class="nav-item" data-page="tenants">
|
|
<i class="fas fa-building"></i>
|
|
<span>Tenants</span>
|
|
<span class="badge" id="tenant-count">0</span>
|
|
</a>
|
|
<a href="#/health" class="nav-item" data-page="health">
|
|
<i class="fas fa-heartbeat"></i>
|
|
<span>Salud</span>
|
|
</a>
|
|
<a href="#/migrations" class="nav-item" data-page="migrations">
|
|
<i class="fas fa-database"></i>
|
|
<span>Migraciones</span>
|
|
</a>
|
|
</nav>
|
|
<div class="sidebar-footer">
|
|
<div class="user-info">
|
|
<span id="user-email">admin</span>
|
|
<button onclick="logout()" class="btn-icon" title="Cerrar sesión">
|
|
<i class="fas fa-sign-out-alt"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
|
|
<!-- Content -->
|
|
<main class="main">
|
|
<header class="topbar">
|
|
<h2 id="page-title">Dashboard</h2>
|
|
<div class="topbar-actions">
|
|
<span class="status-indicator" id="system-status">
|
|
<i class="fas fa-circle"></i> Online
|
|
</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="content">
|
|
<!-- Dashboard Page -->
|
|
<section id="page-dashboard" class="page">
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-icon bg-blue"><i class="fas fa-building"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="stat-total">0</h3>
|
|
<p>Total Tenants</p>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon bg-green"><i class="fas fa-check-circle"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="stat-active">0</h3>
|
|
<p>Activos</p>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon bg-purple"><i class="fas fa-rocket"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="stat-demos">0</h3>
|
|
<p>Demos</p>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-icon bg-orange"><i class="fas fa-clock"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="stat-expiring">0</h3>
|
|
<p>Expiran pronto</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-server"></i> Estado del Sistema</h3>
|
|
</div>
|
|
<div class="card-body" id="system-health-summary">
|
|
<div class="loading">Cargando...</div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-building"></i> Demos Recientes</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table compact">
|
|
<thead>
|
|
<tr><th>Nombre</th><th>Subdominio</th><th>Expira</th><th>Estado</th></tr>
|
|
</thead>
|
|
<tbody id="recent-demos-table">
|
|
<tr><td colspan="4" class="text-muted">Cargando...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Demos Page -->
|
|
<section id="page-demos" class="page" style="display:none;">
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-plus-circle"></i> Nueva Demo</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<form id="demo-form">
|
|
<div class="form-group">
|
|
<label>Nombre del negocio *</label>
|
|
<input type="text" id="demo-name" required placeholder="Refaccionaria López">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Email de contacto</label>
|
|
<input type="email" id="demo-email" placeholder="cliente@email.com">
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Días de vigencia</label>
|
|
<input type="number" id="demo-days" value="14" min="1" max="90">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>PIN del owner</label>
|
|
<input type="text" id="demo-pin" value="0000" maxlength="10">
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Subdominio (opcional)</label>
|
|
<div class="input-group">
|
|
<input type="text" id="demo-subdomain" placeholder="refaccionaria-lopez">
|
|
<span class="input-suffix">.nexusautoparts.com.mx</span>
|
|
</div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-rocket"></i> Crear Demo
|
|
</button>
|
|
</form>
|
|
<div id="demo-result" class="result-box" style="display:none;"></div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-list"></i> Demos Activas</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table">
|
|
<thead>
|
|
<tr><th>Negocio</th><th>URL</th><th>Días rest.</th><th>Acciones</th></tr>
|
|
</thead>
|
|
<tbody id="demos-table">
|
|
<tr><td colspan="4" class="text-muted">Cargando...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Tenants Page -->
|
|
<section id="page-tenants" class="page" style="display:none;">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-building"></i> Todos los Tenants</h3>
|
|
<div class="card-actions">
|
|
<input type="text" id="tenant-search" placeholder="Buscar..." class="input-sm">
|
|
<button class="btn btn-sm btn-secondary" onclick="loadTenants(true)">
|
|
<i class="fas fa-sync"></i> Refrescar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table">
|
|
<thead>
|
|
<tr>
|
|
<th>ID</th>
|
|
<th>Nombre</th>
|
|
<th>Subdominio</th>
|
|
<th>Plan</th>
|
|
<th>Versión</th>
|
|
<th>Estado</th>
|
|
<th>Creado</th>
|
|
<th>Acciones</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tenants-table">
|
|
<tr><td colspan="8" class="text-muted">Cargando...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Health Page -->
|
|
<section id="page-health" class="page" style="display:none;">
|
|
<div class="grid-3">
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-database"></i> PostgreSQL</h3></div>
|
|
<div class="card-body" id="health-postgresql"><div class="loading">...</div></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-bolt"></i> Redis</h3></div>
|
|
<div class="card-body" id="health-redis"><div class="loading">...</div></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-hdd"></i> Disco</h3></div>
|
|
<div class="card-body" id="health-disk"><div class="loading">...</div></div>
|
|
</div>
|
|
</div>
|
|
<div class="grid-2">
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-memory"></i> Memoria</h3></div>
|
|
<div class="card-body" id="health-memory"><div class="loading">...</div></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-cogs"></i> Servicios Systemd</h3></div>
|
|
<div class="card-body" id="health-services"><div class="loading">...</div></div>
|
|
</div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-header"><h3><i class="fas fa-network-wired"></i> Servicios HTTP</h3></div>
|
|
<div class="card-body" id="health-http"><div class="loading">...</div></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Migrations Page -->
|
|
<section id="page-migrations" class="page" style="display:none;">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-database"></i> Migraciones de Schema</h3>
|
|
<div class="card-actions">
|
|
<button class="btn btn-primary" onclick="runAllMigrations()">
|
|
<i class="fas fa-play"></i> Ejecutar todas pendientes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="migration-log" class="log-box" style="display:none;"></div>
|
|
<table class="table">
|
|
<thead>
|
|
<tr><th>Tenant</th><th>DB</th><th>Versión actual</th><th>Estado</th></tr>
|
|
</thead>
|
|
<tbody id="migrations-table">
|
|
<tr><td colspan="4" class="text-muted">Cargando...</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Modal -->
|
|
<div id="modal" class="modal" style="display:none;">
|
|
<div class="modal-overlay" onclick="closeModal()"></div>
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h3 id="modal-title">Confirmar</h3>
|
|
<button class="btn-icon" onclick="closeModal()"><i class="fas fa-times"></i></button>
|
|
</div>
|
|
<div class="modal-body" id="modal-body"></div>
|
|
<div class="modal-footer" id="modal-footer">
|
|
<button class="btn btn-secondary" onclick="closeModal()">Cancelar</button>
|
|
<button class="btn btn-danger" id="modal-confirm-btn">Confirmar</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Toast -->
|
|
<div id="toast-container"></div>
|
|
|
|
<script src="/static/js/manager.js"></script>
|
|
</body>
|
|
</html>
|