gp
This commit is contained in:
@@ -1,57 +1,86 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="es">
|
||||
|
||||
<head>
|
||||
<title>Main</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>U-site — Acceso</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.5/dist/cdn.min.js" defer></script>
|
||||
<style>
|
||||
.profile-action:hover .profile-action-dropdown {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
aside ul li:hover .menu-item-text {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
margin-left: 7rem;
|
||||
width: 10rem;
|
||||
padding: 22px;
|
||||
background-color: rgba(247, 250, 252, var(--bg-opacity));
|
||||
}
|
||||
|
||||
aside .logout:hover .menu-item-text {
|
||||
display: inline-block;
|
||||
padding: 22px;
|
||||
position: absolute;
|
||||
margin-left: 7rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
.date:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
top: 12px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.date:after {
|
||||
margin-left: 15px;
|
||||
}
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body { font-family: 'Inter', system-ui, sans-serif; }
|
||||
[x-cloak] { display: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="min-h-screen flex" style="background-color:#f1f5f9">
|
||||
|
||||
<body class="bg-gray-200 h-screen w-screen flex">
|
||||
<div class="flex-1 flex flex-col" x-data="{ open: false }">
|
||||
<button class="bg-blue-500 text-white p-4" @click="open = !open">Toggle Dropdown</button>
|
||||
<div x-show="open" class="mt-2 p-4 bg-white shadow-lg">
|
||||
Dropdown Content
|
||||
<!-- Left panel: brand -->
|
||||
<div class="hidden lg:flex lg:w-1/2 xl:w-2/5 flex-col justify-between p-12"
|
||||
style="background: linear-gradient(145deg, #1e293b 0%, #0f172a 100%)">
|
||||
<!-- Top: logo -->
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0"
|
||||
style="background-color:#8eb02f">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253M3 12c0 .778.099 1.533.284 2.253"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-white font-bold text-xl tracking-tight">U-site</span>
|
||||
</div>
|
||||
|
||||
{{template "partials/landing-header" .}}
|
||||
<!-- Middle: tagline -->
|
||||
<div>
|
||||
<h2 class="text-white text-3xl font-bold leading-snug mb-4">
|
||||
Gestiona tu plataforma<br>
|
||||
<span style="color:#8eb02f">con total control</span>
|
||||
</h2>
|
||||
<p class="text-slate-400 text-sm leading-relaxed max-w-xs">
|
||||
Panel de administración seguro, rápido e intuitivo para gestionar usuarios, servidores, módulos y más.
|
||||
</p>
|
||||
|
||||
<!-- Feature bullets -->
|
||||
<ul class="mt-8 space-y-3">
|
||||
<li class="flex items-center gap-3 text-sm text-slate-400">
|
||||
<div class="w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0" style="background-color:rgba(142,176,47,.2)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" style="color:#8eb02f" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
Control de roles y permisos
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-400">
|
||||
<div class="w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0" style="background-color:rgba(142,176,47,.2)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" style="color:#8eb02f" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
Gestión de servidores y conexiones
|
||||
</li>
|
||||
<li class="flex items-center gap-3 text-sm text-slate-400">
|
||||
<div class="w-5 h-5 rounded-full flex items-center justify-center flex-shrink-0" style="background-color:rgba(142,176,47,.2)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" style="color:#8eb02f" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
Sesiones seguras con tiempo de expiración
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Bottom: copyright -->
|
||||
<p class="text-slate-600 text-xs">© 2025 U-site. Todos los derechos reservados.</p>
|
||||
</div>
|
||||
|
||||
<!-- Right panel: form -->
|
||||
<div class="flex-1 flex items-center justify-center p-6 sm:p-10">
|
||||
{{embed}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -5,59 +5,219 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>U-site</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<!-- Select2 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet"/>
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="/css/app.css">
|
||||
<!-- Axios -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.7/dist/axios.min.js"></script>
|
||||
<!-- Alpine.js -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.5/dist/cdn.min.js" defer></script>
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Cargar jQuery primero -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<!-- Luego cargar Select2 -->
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
||||
<!-- Select2 JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<style>
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
body { font-family: 'Inter', system-ui, -apple-system, sans-serif; }
|
||||
[x-cloak] { display: none !important; }
|
||||
|
||||
:root {
|
||||
--color-primary: #8eb02f;
|
||||
--color-primary-dark: #6d8c24;
|
||||
--color-secondary: #1e293b;
|
||||
--color-bg: #f1f5f9;
|
||||
--color-border: #e2e8f0;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* Sidebar scroll */
|
||||
.sidebar-scroll { scrollbar-width: none; -ms-overflow-style: none; }
|
||||
.sidebar-scroll::-webkit-scrollbar { display: none; }
|
||||
|
||||
/* Avatar circle */
|
||||
.ui-avatar {
|
||||
width: 2rem; height: 2rem; border-radius: 9999px;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
font-weight: 700; font-size: .7rem; color: #fff;
|
||||
background-color: var(--color-primary);
|
||||
text-transform: uppercase; flex-shrink: 0; user-select: none;
|
||||
}
|
||||
.ui-avatar-lg { width: 2.25rem; height: 2.25rem; font-size: .8rem; }
|
||||
|
||||
/* Pulse animation for activity dot */
|
||||
@keyframes pulse-ring {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: .75; transform: scale(1.15); }
|
||||
}
|
||||
.pulse-dot { animation: pulse-ring 2.5s ease-in-out infinite; }
|
||||
|
||||
/* Smooth focus inputs */
|
||||
.ui-input {
|
||||
width: 100%; padding: .5rem .75rem;
|
||||
border: 1px solid var(--color-border); border-radius: var(--radius);
|
||||
font-size: .875rem; outline: none; font-family: inherit;
|
||||
transition: border-color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.ui-input:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(142, 176, 47, .15);
|
||||
}
|
||||
.ui-input::placeholder { color: #94a3b8; }
|
||||
|
||||
/* Active nav item in sidebar */
|
||||
.nav-active { background-color: var(--color-primary) !important; color: #fff !important; }
|
||||
|
||||
/* === Global content page improvements === */
|
||||
|
||||
/* Card wrapper (used in all content pages) */
|
||||
.bg-white.rounded-lg.shadow,
|
||||
.bg-white.rounded-lg {
|
||||
border-radius: 0.75rem !important;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04) !important;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
/* Page title */
|
||||
h1.text-2xl.font-bold {
|
||||
font-size: 1.125rem !important;
|
||||
font-weight: 700 !important;
|
||||
color: #1e293b !important;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
/* Search input improvements */
|
||||
input[placeholder="Buscar..."] {
|
||||
border-radius: 0.5rem !important;
|
||||
border: 1px solid #e2e8f0 !important;
|
||||
font-size: 0.875rem !important;
|
||||
transition: border-color .15s ease, box-shadow .15s ease !important;
|
||||
outline: none !important;
|
||||
padding: 0.5rem 0.75rem 0.5rem 2rem !important;
|
||||
}
|
||||
input[placeholder="Buscar..."]:focus {
|
||||
border-color: var(--color-primary) !important;
|
||||
box-shadow: 0 0 0 3px rgba(142,176,47,.12) !important;
|
||||
}
|
||||
|
||||
/* Table - global improvements */
|
||||
.table-auto, table.table-auto {
|
||||
border-collapse: collapse;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.table-auto thead tr, table.table-auto thead tr {
|
||||
background-color: #f8fafc !important;
|
||||
}
|
||||
.table-auto thead th, table.table-auto thead th {
|
||||
font-size: 0.7rem !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.05em !important;
|
||||
color: #475569 !important;
|
||||
padding: 0.75rem 1rem !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.table-auto tbody td, table.table-auto tbody td {
|
||||
padding: 0.875rem 1rem !important;
|
||||
vertical-align: middle;
|
||||
font-size: 0.8125rem !important;
|
||||
}
|
||||
.table-auto tbody tr, table.table-auto tbody tr {
|
||||
transition: background-color 0.1s ease;
|
||||
}
|
||||
.table-auto tbody tr:hover, table.table-auto tbody tr:hover {
|
||||
background-color: #f8fafc !important;
|
||||
}
|
||||
|
||||
/* Action icons in rows */
|
||||
.table-auto button svg, table.table-auto button svg {
|
||||
transition: transform 0.1s ease;
|
||||
}
|
||||
.table-auto button:hover svg, table.table-auto button:hover svg {
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
/* Loading overlay */
|
||||
.fixed.inset-0.bg-gray-800 {
|
||||
background-color: rgba(15, 23, 42, 0.55) !important;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
/* Page loading bar at top */
|
||||
.page-content { animation: fadeIn .2s ease; }
|
||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-200 w-full">
|
||||
<main class="flex justify-between w-full">
|
||||
<!-- Componente Lateral (Barra Lateral) -->
|
||||
<body class="bg-slate-100 w-full">
|
||||
<main class="flex w-full">
|
||||
<!-- Sidebar -->
|
||||
{{template "partials/lateral" .}}
|
||||
<div class="w-full">
|
||||
<!-- Main area -->
|
||||
<div class="flex-1 flex flex-col min-w-0">
|
||||
{{template "partials/header" .}}
|
||||
<div class="w-full px-3 py-3 h-[calc(100vh-64px)] overflow-auto">
|
||||
<div class="flex-1 p-4 overflow-auto page-content" style="height:calc(100vh - 64px)">
|
||||
{{embed}}
|
||||
</div>
|
||||
{{template "partials/footer" .}}
|
||||
</div>
|
||||
</main>
|
||||
<div x-data="sessionTimeout" x-init="init()" x-cloak class="hidden sm:block">
|
||||
<!-- Contador General de Inactividad -->
|
||||
<div x-show="!showModal" class="fixed top-2 right-20 border text rounded-lg p-2 bg-gray-100 text-sm">
|
||||
<p>Tiempo restante: <span x-text="formatTime(inactivityCountdown)"></span></p>
|
||||
<!-- Session Timeout -->
|
||||
<div x-data="sessionTimeout" x-init="init()" x-cloak>
|
||||
<!-- Activity pill -->
|
||||
<div x-show="!showModal"
|
||||
x-transition
|
||||
class="fixed top-4 right-4 z-50 hidden sm:flex items-center gap-2 bg-white border border-slate-200 shadow-sm rounded-full px-3 py-1.5 text-xs text-slate-500 select-none">
|
||||
<div class="w-2 h-2 rounded-full bg-emerald-400 pulse-dot flex-shrink-0"></div>
|
||||
<span>Sesión: <span class="font-semibold text-slate-700" x-text="formatTime(inactivityCountdown)"></span></span>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div x-show="showModal" class="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-75">
|
||||
<div class="bg-white rounded-lg shadow-md p-8 w-[600px] relative">
|
||||
<h2 class="text-xl font-bold mb-6 text-center">¿Deseas continuar?</h2>
|
||||
<p class="text-gray-600 mb-6 text-center text-base">
|
||||
Has estado inactivo. Elige "Continuar" para seguir con tu sesión o "Cerrar sesión" para salir.
|
||||
</p>
|
||||
<!-- Contador del tiempo para responder -->
|
||||
<p class="text-center text-red-500 font-semibold mb-4">
|
||||
Tiempo para responder: <span x-text="countdown"></span> seg
|
||||
</p>
|
||||
<div class="flex justify-around">
|
||||
<button @click="continueSession"
|
||||
class="px-6 py-3 bg-[#8eb02f] text-white text-base rounded-lg hover:bg-[#698126]">
|
||||
Continuar
|
||||
</button>
|
||||
<form action="/do/logout" method="POST" class="inline-block">
|
||||
<button type="submit"
|
||||
class="px-6 py-3 bg-gray-500 text-white text-base rounded-lg hover:bg-gray-600">
|
||||
Cerrar sesión
|
||||
<div x-show="showModal"
|
||||
x-transition
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/60 backdrop-blur-sm p-4">
|
||||
<div class="bg-white rounded-2xl shadow-2xl w-full max-w-md overflow-hidden">
|
||||
<div class="px-6 py-5" style="background: linear-gradient(135deg, #1e293b 0%, #334155 100%)">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0"
|
||||
style="background:rgba(251,191,36,.15)">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" style="color:#fbbf24" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-white font-semibold">Sesión por vencer</h2>
|
||||
<p class="text-slate-400 text-xs mt-0.5">Tu sesión expirará por inactividad</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 py-6">
|
||||
<div class="flex items-center justify-center gap-2 bg-red-50 rounded-xl py-3 mb-5 border border-red-100">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-red-500 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<span class="text-sm text-red-600">Cierre automático en <strong x-text="countdown"></strong> segundos</span>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button @click="continueSession"
|
||||
class="flex-1 py-2.5 rounded-xl text-sm font-semibold text-white transition-all"
|
||||
style="background-color:#8eb02f"
|
||||
onmouseover="this.style.backgroundColor='#6d8c24'"
|
||||
onmouseout="this.style.backgroundColor='#8eb02f'">
|
||||
Continuar sesión
|
||||
</button>
|
||||
</form>
|
||||
<form action="/do/logout" method="POST" class="flex-1">
|
||||
<button type="submit"
|
||||
class="w-full py-2.5 rounded-xl text-sm font-semibold bg-slate-100 text-slate-700 hover:bg-slate-200 transition-colors">
|
||||
Cerrar sesión
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,22 +227,20 @@
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("sessionTimeout", () => ({
|
||||
showModal: false,
|
||||
countdown: 60, // 10 segundos para responder
|
||||
inactivityCountdown: 300, // 5 minutos
|
||||
countdown: 60,
|
||||
inactivityCountdown: 300,
|
||||
inactivityTimer: null,
|
||||
timer: null,
|
||||
|
||||
init() {
|
||||
this.resetInactivityTimer();
|
||||
|
||||
// Detectar actividad
|
||||
window.addEventListener("mousemove", () => this.resetInactivityTimer());
|
||||
window.addEventListener("keydown", () => this.resetInactivityTimer());
|
||||
window.addEventListener("keydown", () => this.resetInactivityTimer());
|
||||
},
|
||||
|
||||
resetInactivityTimer() {
|
||||
clearTimeout(this.inactivityTimer);
|
||||
this.inactivityCountdown = 300; // Reiniciar contador a 5 minutos
|
||||
this.inactivityCountdown = 300;
|
||||
this.startInactivityTimer();
|
||||
},
|
||||
|
||||
@@ -102,13 +260,11 @@
|
||||
},
|
||||
|
||||
startCountdown() {
|
||||
this.countdown = 60; // Reiniciar contador del modal
|
||||
this.countdown = 60;
|
||||
clearInterval(this.timer);
|
||||
this.timer = setInterval(() => {
|
||||
this.countdown--;
|
||||
if (this.countdown <= 0) {
|
||||
this.logout();
|
||||
}
|
||||
if (this.countdown <= 0) this.logout();
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
@@ -124,9 +280,9 @@
|
||||
},
|
||||
|
||||
formatTime(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
|
||||
const m = Math.floor(seconds / 60);
|
||||
const s = seconds % 60;
|
||||
return `${m}:${s.toString().padStart(2, "0")}`;
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user