// Admin view: audit log + user management function UsersTab({ user }) { const [users, setUsers] = React.useState([]); const [invites, setInvites] = React.useState([]); const [loading, setLoading] = React.useState(true); const [inviteEmail, setInviteEmail] = React.useState(''); const [inviteRole, setInviteRole] = React.useState('operator'); const [inviteStatus, setInviteStatus] = React.useState(null); // null | 'loading' | 'ok' | 'error' const [inviteErr, setInviteErr] = React.useState(''); const [inviteLink, setInviteLink] = React.useState(''); const [patchStatus, setPatchStatus] = React.useState({}); // {userId: 'loading'|'ok'|'error'} async function load() { try { const [uRes, iRes] = await Promise.all([fetch('/users'), fetch('/users/invitations')]); if (uRes.ok) setUsers(await uRes.json()); if (iRes.ok) setInvites(await iRes.json()); } catch (_) {} setLoading(false); } React.useEffect(() => { load(); }, []); async function sendInvite(e) { e?.preventDefault?.(); if (!inviteEmail) return; setInviteStatus('loading'); setInviteErr(''); try { const res = await fetch('/users/invite', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: inviteEmail, role: inviteRole }), }); if (!res.ok) { const data = await res.json().catch(() => ({})); setInviteStatus('error'); setInviteErr(data.detail || 'Error al enviar invitación.'); return; } const data = await res.json(); setInviteLink(data.link || ''); setInviteStatus('ok'); setInviteEmail(''); load(); } catch (_) { setInviteStatus('error'); setInviteErr('Error de conexión.'); } } async function revokeInvite(id) { try { await fetch(`/users/invitations/${id}`, { method: 'DELETE' }); load(); } catch (_) {} } async function toggleActive(u) { setPatchStatus(p => ({ ...p, [u.id]: 'loading' })); try { const res = await fetch(`/users/${u.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ active: !u.active }), }); setPatchStatus(p => ({ ...p, [u.id]: res.ok ? 'ok' : 'error' })); if (res.ok) load(); setTimeout(() => setPatchStatus(p => { const n={...p}; delete n[u.id]; return n; }), 2000); } catch (_) { setPatchStatus(p => ({ ...p, [u.id]: 'error' })); } } const isMe = (u) => u.id === user.id; const roleColors = { admin: { bg: BC.supervisory.bg, fg: BC.supervisory.fg }, operator: { bg: BC.trouble.bg, fg: BC.trouble.fg } }; function RoleChip({ role }) { const c = roleColors[role] || { bg: BC.bgAlt, fg: BC.inkSubtle }; return {role}; } const formatDate = ts => ts ? new Date(ts).toLocaleDateString('es-CL') : '—'; return (
{/* Invite panel */}
Invitar usuario
El usuario recibirá un enlace para crear su cuenta. La invitación expira en 72 horas.
setInviteEmail(e.target.value)} placeholder="correo@empresa.com" style={{ width:'100%', padding:'8px 10px', border:'1px solid '+BC.border, borderRadius:5, background:BC.surfaceAlt, fontSize:13, color:BC.ink }}/>
{inviteStatus === 'ok' && inviteLink && (
Invitación creada. Comparte este enlace con el usuario:
e.target.select()}/>
)} {inviteStatus === 'error' && (
{inviteErr}
)}
{/* Pending invitations */} {invites.length > 0 && (
Invitaciones pendientes {invites.length}
{invites.map(inv => ( ))}
CorreoRolExpira
{inv.email} {formatDate(inv.expires_at)}
)} {/* Users table */}
Usuarios
Usuarios activos e inactivos de tu empresa.
{loading ? (
Cargando…
) : (
{users.length === 0 && ( )} {users.map(u => ( ))}
UsuarioCorreoRolEstado
Sin usuarios.
{u.username} {isMe(u) && (tú)} {u.email || '—'} {u.active ? 'Activo' : 'Inactivo'} {!isMe(u) && ( )}
)}
); } function AdminView({ user }) { const [tab, setTab] = React.useState('audit'); const [audit, setAudit] = React.useState([]); const [loading, setLoading] = React.useState(true); React.useEffect(() => { let cancelled = false; async function fetchAudit() { try { const res = await fetch('/audit?limit=100'); if (!res.ok) return; const data = await res.json(); if (!cancelled) setAudit(data); } catch (_) {} if (!cancelled) setLoading(false); } fetchAudit(); const t = setInterval(fetchAudit, 5000); return () => { cancelled = true; clearInterval(t); }; }, []); const tabs = [ { id: 'audit', label: 'Auditoría', icon: 'terminal', count: audit.length }, ...(user.role === 'admin' ? [{ id: 'users', label: 'Usuarios', icon: 'user', count: null }] : []), ]; return (
Administración
Registro de auditoría de acciones de usuarios y comandos enviados a paneles.
{tabs.map(t => ( ))}
{tab === 'users' && } {tab === 'audit' && (
Registro de auditoría
Acciones de usuarios y comandos enviados a paneles.
{loading ? (
Cargando…
) : (
{audit.length === 0 && ( )} {audit.map(a => { const isError = !a.success; return ( ); })}
FechaAcciónUsuarioPanelMensajeResultado
Sin registros de auditoría.
{formatTs(a.at, { full:true })} {a.action} {a.user} {a.client_description || '—'} {a.message || '—'} {isError ? 'Error' : 'OK'}
)}
)}
); } window.AdminView = AdminView;