/* global React */ // ======================================================================= // BlazeConnector Admin v3 — AppShell (Sidebar + Topbar) // ======================================================================= const { useState: _useState, useEffect: _useEffect, useRef: _useRef, useMemo: _useMemo } = React; const NAV_GROUPS = [ { label: 'Operaciones', items: [ { id: 'dashboard', label: 'Dashboard', icon: 'gauge', path: '/dashboard', perms: ['view:dashboard'] }, { id: 'messaging', label: 'Mensajería', icon: 'message-square',path: '/messaging', perms: ['view:messaging'] }, { id: 'queues', label: 'Colas', icon: 'layers', path: '/queues', perms: ['view:queues'] }, { id: 'logs', label: 'Logs', icon: 'terminal', path: '/logs', perms: ['view:logs'] }, { id: 'alerts', label: 'Alertas', icon: 'bell-ring', path: '/alerts', perms: ['view:alerts'] } ]}, { label: 'Administración', items: [ { id: 'tenants', label: 'Tenants', icon: 'building-2', path: '/tenants', perms: ['view:tenants'] }, { id: 'apikeys', label: 'API Keys', icon: 'key-round', path: '/apikeys', perms: ['view:apikeys'] }, { id: 'templates', label: 'Templates', icon: 'file-text', path: '/templates', perms: ['view:templates'] }, { id: 'audit', label: 'Auditoría', icon: 'shield-check', path: '/audit', perms: ['view:audit'] } ]}, { label: 'Integraciones', items: [ { id: 'isps', label: 'ISP Providers', icon: 'router', path: '/isps', perms: ['view:integrations'] }, { id: 'pagos', label: 'Pagos', icon: 'credit-card',path: '/pagos', perms: ['view:integrations'] } ]} ]; const ROLE_DEFS = { superadmin: { label: 'Superadmin', perms: '*', desc: 'Acceso total al sistema' }, operaciones: { label: 'Operaciones', desc: 'CRUD operacional, retries, configuración', perms: ['view:dashboard','view:messaging','view:queues','view:logs','view:alerts','view:tenants','view:apikeys','view:templates','view:audit','view:integrations','retry:execute','tenant:edit','apikey:create','apikey:revoke','template:sync','alert:ack','config:edit'] }, soporte: { label: 'Soporte', desc: 'Lectura + retries + lookups operacionales', perms: ['view:dashboard','view:messaging','view:queues','view:logs','view:alerts','view:tenants','view:audit','retry:execute','alert:ack'] }, readonly: { label: 'Read-only', desc: 'Solo lectura, sin acciones', perms: ['view:dashboard','view:messaging','view:queues','view:logs','view:alerts','view:tenants','view:apikeys','view:templates','view:audit','view:integrations'] } }; function hasPerm(role, perm) { const def = ROLE_DEFS[role]; if (!def) return false; if (def.perms === '*') return true; return def.perms.includes(perm); } const TENANT_GRAD = (id) => { const grads = ['linear-gradient(135deg,#FF5A1F,#F000D8)', 'linear-gradient(135deg,#1E7BFF,#7A3CFF)', 'linear-gradient(135deg,#2ECC5A,#16C6B9)', 'linear-gradient(135deg,#F000D8,#7A3CFF)', 'linear-gradient(135deg,#FF5A1F,#F2A900)', 'linear-gradient(135deg,#16C6B9,#1E7BFF)']; return grads[(id || '').charCodeAt(4) % grads.length]; }; /* ---------------- Sidebar ------------------------------------------ */ function Sidebar({ activePath, onNav, role, badges }) { return ( ); } /* ---------------- Tenant switcher popover -------------------------- */ function TenantSwitcher({ tenant, onSelect }) { const [open, setOpen] = _useState(false); const [q, setQ] = _useState(''); const ref = _useRef(null); const bc = window.useBC(); // re-render cuando cambian los tenants (live) _useEffect(() => { if (!open) return; const fn = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', fn); return () => document.removeEventListener('mousedown', fn); }, [open]); const list = _useMemo(() => { const ql = q.toLowerCase(); return [{ id: '*', name: 'Todos los tenants', uuid: 'global', status: 'active' }, ...bc.tenants].filter(t => !ql || t.name.toLowerCase().includes(ql) || (t.id || '').includes(ql)); }, [q, bc.tenants]); return (
{open && (
setQ(e.target.value)} placeholder="Buscar tenant…" autoFocus />
Tenants ({list.length - 1})
{list.map((t) => ( ))}
)}
); } /* ---------------- User menu / role switcher ------------------------ */ function UserMenu({ role, onRoleChange, theme, onThemeToggle }) { const [open, setOpen] = _useState(false); const ref = _useRef(null); _useEffect(() => { if (!open) return; const fn = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener('mousedown', fn); return () => document.removeEventListener('mousedown', fn); }, [open]); return (
{open && (
YR
Yarisbel Rodríguez
yarisbel@blaze.do
Cambiar rol (demo)
{Object.entries(ROLE_DEFS).map(([k, def]) => ( ))}
)}
); } /* ---------------- Realtime indicator ------------------------------- */ function RealtimeIndicator({ status, eventsPerSec }) { const tone = status === 'connected' ? '' : status === 'degraded' ? 'is-degraded' : 'is-down'; const lbl = status === 'connected' ? 'live' : status === 'degraded' ? 'degraded' : 'down'; return ( {lbl} {eventsPerSec.toFixed(1)}/s ); } /* ---------------- Topbar ------------------------------------------- */ function Topbar({ tenant, onTenantChange, breadcrumb, role, onRoleChange, theme, onThemeToggle, realtime, onSidebarToggle, onOpenCmdK }) { return (
{breadcrumb.map((c, i) => ( {i > 0 && /} {c} ))}
K
); } /* ---------------- Command palette (⌘K) ----------------------------- */ function CommandPalette({ open, onClose, onNav }) { const [q, setQ] = _useState(''); _useEffect(() => { if (open) setQ(''); }, [open]); if (!open) return null; const items = [ ...NAV_GROUPS.flatMap((g) => g.items.map(it => ({ type: 'Página', label: it.label, sub: g.label, ico: it.icon, action: () => onNav(it.path) }))), ...window.TENANTS.slice(0, 8).map((t) => ({ type: 'Tenant', label: t.name, sub: t.id + ' · ' + t.region, ico: 'building-2', action: () => onNav('/tenants/' + t.id) })), { type: 'Acción', label: 'Crear nuevo tenant', sub: 'tenants', ico: 'plus', action: () => onNav('/tenants?new=1') }, { type: 'Acción', label: 'Emitir API key', sub: 'apikeys', ico: 'key-round', action: () => onNav('/apikeys?new=1') }, { type: 'Acción', label: 'Ver mensajes fallidos', sub: 'messaging', ico: 'message-square-warning', action: () => onNav('/messaging?status=failed') } ]; const ql = q.toLowerCase(); const filtered = !ql ? items : items.filter(it => it.label.toLowerCase().includes(ql) || it.sub.toLowerCase().includes(ql)); return (
e.stopPropagation()} style={{ maxWidth: 560, padding: 0 }}>
setQ(e.target.value)} placeholder="Buscar páginas, tenants, acciones, trace_ids…" autoFocus style={{ flex: 1, border: 'none', background: 'transparent', outline: 'none', color: 'var(--app-fg1)', fontSize: 14 }} /> ESC
{filtered.length === 0 &&
Sin coincidencias
Prueba con otro término o filtro.
} {filtered.map((it, i) => ( ))}
↑↓ navegar abrir {filtered.length} resultados
); } Object.assign(window, { Sidebar, Topbar, CommandPalette, NAV_GROUPS, ROLE_DEFS, hasPerm, TENANT_GRAD });