{# Panel de usuarios + roles + audit. Contrato con el router `make_users_router`: mount_prefix → prefijo del router (ej: "/admin/users") catalog → dict {permission_key: human_label} directory_caps → capacidades del directorio de identidades (opcional) Todos los endpoints son relativos a `mount_prefix`: GET {mount_prefix}/data → JSON users POST {mount_prefix}/invite → form invite POST {mount_prefix}/create → form crear con password PATCH {mount_prefix}/{user_id}/roles → JSON roles POST {mount_prefix}/{user_id}/password/reset POST {mount_prefix}/{user_id}/password/set DELETE {mount_prefix}/{user_id} GET {mount_prefix}/{user_id}/effective-permissions GET {mount_prefix}/roles POST {mount_prefix}/roles PATCH {mount_prefix}/roles/{slug}/permissions DELETE {mount_prefix}/roles/{slug} GET {mount_prefix}/audit La página carga los datos en JS vía fetch para evitar acoplar el render server-side a un layout específico del host. El host es responsable de envolver este template con su propio layout (sidebar, header, etc.) y de cargar `htmx-events.js` (dgConfirm), `toasts.js` (dgToast; sin la región `#dg-toasts` los avisos van a alert()) y `reveal.js` (mostrar contraseña). `sidebar_shell` ya los carga. Los estilos propios viven en el bloque dg-users de `_tailwind.source.css`; pila, pestañas, diálogos, avisos y campos son componentes del paquete. #} {% from "core/components/page_header.html" import page_header %} {% from "core/components/search_field.html" import search_field %} {% from "core/components/icon.html" import icon, icon_tile %} {% from "core/components/skeleton.html" import skeleton %} {% set base = mount_prefix | default('/admin/users') %} {# Capabilities del directorio de identidades (Supabase/Clerk). El default replica el set completo de Supabase para contextos que no las pasan. #} {% set caps = directory_caps | default(['invite', 'create_password', 'password_reset_email', 'password_set', 'delete']) %} {# `cols` lleva la clase de cada columna: las que se ocultan en anchos estrechos deben ocultarse también en el esqueleto o aparece una columna sin cabecera. La fecha de Actividad lleva ancho fijo: al apilarse, su celda se ajusta al contenido y un porcentaje la dejaría a cero. #} {% macro _skeleton_rows(cols, lead=false) -%} {%- for _ in range(3) %}
{%- endfor %} {%- endmacro %} {# El subtítulo describe el diálogo (aria-describedby="{{ caller() }}
{{ icon("circle-alert") }}
{%- endmacro %} {% set _meta = [ {"icon": "users", "label": '… usuarios' | safe}, {"icon": "circle-dashed", "label": '' | safe}, ] %}| Usuario | Roles | Acciones |
|---|
| Rol | Permisos | Acciones |
|---|
| Fecha | Acción | Hecho por | Usuario afectado | Detalle |
|---|