228 lines
9.4 KiB
JavaScript
228 lines
9.4 KiB
JavaScript
// Layout común: navbar superior + footer + funcionalidades globales (Vanilla JS)
|
|
(function () {
|
|
'use strict';
|
|
const LAYOUT_VERSION = 'layout-v1054-vanilla';
|
|
const CSS_VERSION = '20251023-1000';
|
|
try { console.log('[ESP]', LAYOUT_VERSION); } catch {}
|
|
// ===== Theme (light/dark) =====
|
|
const THEME_KEY = 'esp_theme';
|
|
function applyTheme(theme) {
|
|
const t = (theme === 'dark') ? 'dark' : 'light';
|
|
document.documentElement.setAttribute('data-bs-theme', t);
|
|
try { localStorage.setItem(THEME_KEY, t); } catch {}
|
|
// Toggle de tema removido
|
|
}
|
|
function initTheme() {
|
|
// Forzar tema claro y limpiar preferencia guardada
|
|
applyTheme('light');
|
|
try { localStorage.removeItem(THEME_KEY); } catch {}
|
|
}
|
|
|
|
function renderNavbar() {
|
|
// No mostrar navbar en la página de login
|
|
try { if (location.pathname.endsWith('login.html') || location.pathname.endsWith('login.php')) return ''; } catch {}
|
|
return `
|
|
<nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top shadow-sm">
|
|
<div class="container-fluid">
|
|
<a class="navbar-brand" href="/">ESP Admin</a>
|
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navMain" aria-controls="navMain" aria-expanded="false" aria-label="Toggle navigation">
|
|
<span class="navbar-toggler-icon"></span>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="navMain">
|
|
<ul class="navbar-nav ms-auto align-items-center gap-2">
|
|
<li class="nav-item">
|
|
<a class="btn btn-outline-primary btn-sm" href="/flash">Flashear ESP8266</a>
|
|
</li>
|
|
<li class="nav-item">
|
|
<button id="navCreateDevice" class="btn btn-primary btn-sm" type="button">Crear dispositivo</button>
|
|
</li>
|
|
<li class="nav-item">
|
|
<button id="navCreateSector" class="btn btn-secondary btn-sm" type="button">Crear sector</button>
|
|
</li>
|
|
<li class="nav-item dropdown">
|
|
<a class="nav-link dropdown-toggle d-flex align-items-center gap-2" href="#" id="userMenuToggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
<span aria-hidden="true">👤</span>
|
|
<span id="navbarUsername">Usuario</span>
|
|
</a>
|
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userMenuToggle">
|
|
<li><a class="dropdown-item" href="#" id="menuPerfil">Perfil</a></li>
|
|
<li><hr class="dropdown-divider"></li>
|
|
<li><button class="dropdown-item text-danger" type="button" id="logoutDropdownBtn">Salir</button></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>`;
|
|
}
|
|
|
|
function renderFooter() {
|
|
const year = new Date().getFullYear();
|
|
return `
|
|
<footer class="site-footer text-center py-3">
|
|
<div class="container small">ESP Project • v1 • ${year}</div>
|
|
</footer>`;
|
|
}
|
|
|
|
function setActiveNav() {
|
|
const isActive = (href) => href && (location.pathname.endsWith(href) || location.href.endsWith(href));
|
|
document.querySelectorAll('.navbar .nav-link').forEach(link => {
|
|
if (isActive(link.getAttribute('href'))) link.classList.add('active');
|
|
});
|
|
}
|
|
|
|
function ensureStylesheet() {
|
|
try {
|
|
const exists = document.querySelector('link[href*="/assets/css/layout.css"]');
|
|
if (exists) return;
|
|
const link = document.createElement('link');
|
|
link.rel = 'stylesheet';
|
|
link.href = `/assets/css/layout.css?v=${CSS_VERSION}`;
|
|
document.head.appendChild(link);
|
|
} catch {}
|
|
}
|
|
// Inyectar CSS lo antes posible para evitar FOUC
|
|
ensureStylesheet();
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
// Asegurar CSS base del layout
|
|
ensureStylesheet();
|
|
const usingPhpLayout = (document.body && document.body.getAttribute('data-layout') === 'php');
|
|
// Insertar navbar y footer solo si no se usa layout.php
|
|
if (!usingPhpLayout) {
|
|
document.body.insertAdjacentHTML('afterbegin', renderNavbar());
|
|
document.body.insertAdjacentHTML('beforeend', renderFooter());
|
|
}
|
|
try {
|
|
console.log('[ESP] DOMContentLoaded, navbar insertado');
|
|
} catch {}
|
|
|
|
// Indicador global de estado MQTT WebSocket (posicionado por CSS en esquina inferior derecha)
|
|
if (!usingPhpLayout && !document.getElementById('wsStatusIndicator')) {
|
|
const wsIndicatorHtml = `
|
|
<div id="wsStatusIndicator" class="ws-indicator inline-flex items-center gap-2 px-3 py-1.5 text-sm rounded-full bg-slate-100 text-slate-700 border border-slate-200 disconnected" title="Estado de conexion MQTT WebSocket">
|
|
<span class="dot w-2.5 h-2.5 rounded-full bg-red-500"></span>
|
|
<span class="label">WS: Desconectado</span>
|
|
</div>`;
|
|
document.body.insertAdjacentHTML('afterbegin', wsIndicatorHtml);
|
|
}
|
|
|
|
// Función global para actualizar el indicador desde cualquier página
|
|
window.updateWSIndicator = function(connected) {
|
|
try {
|
|
const el = document.getElementById('wsStatusIndicator');
|
|
if (!el) return;
|
|
const dot = el.querySelector('.dot');
|
|
const label = el.querySelector('.label');
|
|
|
|
el.classList.remove('connected', 'disconnected');
|
|
if (connected) {
|
|
el.classList.add('connected');
|
|
el.classList.remove('bg-slate-100', 'text-slate-700');
|
|
el.classList.add('bg-emerald-50', 'text-emerald-700', 'border-emerald-200');
|
|
if (dot) {
|
|
dot.classList.remove('bg-red-500');
|
|
dot.classList.add('bg-emerald-500');
|
|
}
|
|
if (label) label.textContent = 'WS: Conectado';
|
|
} else {
|
|
el.classList.add('disconnected');
|
|
el.classList.remove('bg-emerald-50', 'text-emerald-700', 'border-emerald-200');
|
|
el.classList.add('bg-slate-100', 'text-slate-700');
|
|
if (dot) {
|
|
dot.classList.remove('bg-emerald-500');
|
|
dot.classList.add('bg-red-500');
|
|
}
|
|
if (label) label.textContent = 'WS: Desconectado';
|
|
}
|
|
} catch {}
|
|
};
|
|
|
|
const onLoginPage = location.pathname.endsWith('login.html') || location.pathname.endsWith('login.php');
|
|
|
|
// Inicializar tema (se fija en claro por defecto y no hay toggle)
|
|
initTheme();
|
|
|
|
setActiveNav();
|
|
|
|
// Handlers de navbar para abrir modales en index.html o redirigir
|
|
const onIndex = location.pathname.endsWith('index.html') || location.pathname.endsWith('index.php') || /\/paginas\/$/.test(location.pathname) || location.pathname === '/';
|
|
// Helpers globales de modales (Tailwind-only)
|
|
window.openModal = function(id) {
|
|
const m = document.getElementById(id);
|
|
if (!m) return;
|
|
m.classList.remove('hidden');
|
|
// foco overlay click-close
|
|
const overlay = m.querySelector('[data-modal-overlay]');
|
|
if (overlay) overlay.addEventListener('click', () => window.closeModal(id), { once: true });
|
|
if (typeof window.onOpenModal === 'function') {
|
|
try { window.onOpenModal(id); } catch {}
|
|
}
|
|
};
|
|
window.closeModal = function(id) {
|
|
const m = document.getElementById(id);
|
|
if (!m) return;
|
|
m.classList.add('hidden');
|
|
};
|
|
|
|
// Cierre por botones con atributo data-modal-close
|
|
document.addEventListener('click', function(e){
|
|
const btn = e.target.closest('[data-modal-close]');
|
|
if (!btn) return;
|
|
const modal = btn.closest('[id^="modal"]');
|
|
if (modal && modal.id) window.closeModal(modal.id);
|
|
});
|
|
|
|
const navCreateDevice = document.getElementById('navCreateDevice');
|
|
if (navCreateDevice) navCreateDevice.addEventListener('click', function() {
|
|
const modal = document.getElementById('modalCrearDispositivo');
|
|
if (onIndex && modal) { window.openModal('modalCrearDispositivo'); }
|
|
else { location.href = '/'; }
|
|
});
|
|
|
|
const navCreateSector = document.getElementById('navCreateSector');
|
|
if (navCreateSector) navCreateSector.addEventListener('click', function() {
|
|
const modal = document.getElementById('modalGestionSectores');
|
|
if (onIndex && modal) { window.openModal('modalGestionSectores'); }
|
|
else { location.href = '/'; }
|
|
});
|
|
|
|
// Manejadores de logout
|
|
function doLogout() {
|
|
fetch('/api/logout', { method: 'POST', credentials: 'same-origin' })
|
|
.catch(() => {})
|
|
.finally(() => { window.location.href = '/'; });
|
|
}
|
|
const logoutBtn = document.getElementById('logoutBtn');
|
|
if (logoutBtn) logoutBtn.addEventListener('click', doLogout);
|
|
const logoutDropdownBtn = document.getElementById('logoutDropdownBtn');
|
|
if (logoutDropdownBtn) logoutDropdownBtn.addEventListener('click', doLogout);
|
|
|
|
// Comprobación de sesión en páginas protegidas
|
|
if (!onLoginPage) {
|
|
fetch('/api/me', { credentials: 'same-origin' })
|
|
.then(async (res) => {
|
|
if (res.status === 401) {
|
|
window.location.href = '/';
|
|
return;
|
|
}
|
|
try {
|
|
const json = await res.json().catch(() => null);
|
|
const name =
|
|
(json?.data?.user?.nombre) ||
|
|
(json?.data?.user?.name) ||
|
|
(json?.data?.user?.username) ||
|
|
(typeof json?.data?.user === 'string' ? json.data.user : '') ||
|
|
(json?.user?.name) ||
|
|
(json?.user?.username) ||
|
|
(typeof json?.user === 'string' ? json.user : '') ||
|
|
'';
|
|
const el = document.getElementById('navbarUsername');
|
|
if (el && name) el.textContent = name;
|
|
} catch {}
|
|
})
|
|
.catch(() => { /* Si la API no responde, permanecer en la página */ });
|
|
}
|
|
});
|
|
})();
|