💄 (user): Muestra sólo permisos concedidos al rol
This commit is contained in:
parent
f337119219
commit
806ed9e0dc
6 changed files with 78 additions and 64 deletions
|
|
@ -38,19 +38,6 @@
|
||||||
font-size: 0.9375rem;
|
font-size: 0.9375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-admin-permission-list {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-admin-permission-granted {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-admin-permission-missing {
|
|
||||||
color: var(--val-color--text--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-form-error {
|
.user-form-error {
|
||||||
padding: 0.625rem 0.875rem;
|
padding: 0.625rem 0.875rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,15 @@ impl Component for RoleTable {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
let no_permissions_badge = if role.locked || role.has_permissions {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
Badge::neutral(Lc::t("badge-no-permissions", &LOCALES_USER))
|
||||||
|
.render(cx)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
table.alter_row(
|
table.alter_row(
|
||||||
table::Row::new()
|
table::Row::new()
|
||||||
|
|
@ -96,6 +105,7 @@ impl Component for RoleTable {
|
||||||
.with_cell(Html::with(move |_cx| {
|
.with_cell(Html::with(move |_cx| {
|
||||||
html! {
|
html! {
|
||||||
@if let Some(badge) = &system_badge { (badge) }
|
@if let Some(badge) = &system_badge { (badge) }
|
||||||
|
@if let Some(badge) = &no_permissions_badge { (badge) }
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.with_cell(role.user_count.to_string())
|
.with_cell(role.user_count.to_string())
|
||||||
|
|
@ -166,10 +176,9 @@ impl RoleTable {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// URL del listado con el estado actual (orden, página): es el valor que viaja como
|
// URL del listado con el estado actual (orden, página): es el valor que viaja como `waypoint`
|
||||||
// `waypoint` en los enlaces de ver/editar/permisos, para poder volver exactamente a este
|
// en los enlaces de ver/editar/permisos, para poder volver exactamente a este mismo estado. Se
|
||||||
// mismo estado. Se construye con `cx.route()` para que preserve el parámetro `lang` cuando
|
// construye con `cx.route()` para que preserve el parámetro `lang` cuando corresponda.
|
||||||
// corresponda.
|
|
||||||
fn list_href(&self, cx: &Context) -> String {
|
fn list_href(&self, cx: &Context) -> String {
|
||||||
cx.route(ADMIN_ROLES_PATH)
|
cx.route(ADMIN_ROLES_PATH)
|
||||||
.alter_param("sort", self.sort().as_str())
|
.alter_param("sort", self.sort().as_str())
|
||||||
|
|
@ -219,8 +228,8 @@ fn label_cell(role: &RoleListItem, waypoint: &Waypoint) -> Html {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construye la celda de acciones: gestionar permisos siempre, y editar/borrar sólo si el rol no
|
// Construye la celda de acciones: gestionar permisos siempre, y editar/borrar sólo si el rol no
|
||||||
// está bloqueado por el sistema. Devuelve un componente `Html` para que el marcado se genere
|
// está bloqueado por el sistema. Devuelve un componente `Html` para que el marcado se genere cuando
|
||||||
// cuando `Table` renderice la celda, no al construir la fila.
|
// `Table` renderice la celda, no al construir la fila.
|
||||||
async fn actions_cell(
|
async fn actions_cell(
|
||||||
role: &RoleListItem,
|
role: &RoleListItem,
|
||||||
waypoint: &Waypoint,
|
waypoint: &Waypoint,
|
||||||
|
|
@ -235,10 +244,10 @@ async fn actions_cell(
|
||||||
|
|
||||||
let permissions_href = waypoint.append_to(cx.route(role_path(id, "permissions")));
|
let permissions_href = waypoint.append_to(cx.route(role_path(id, "permissions")));
|
||||||
|
|
||||||
// Los botones se renderizan aquí, no dentro del `Html::with()` de abajo: necesitan pasar por
|
// Los botones se renderizan aquí, no dentro del `Html::with()` de abajo: necesitan pasar por su
|
||||||
// su propio ciclo de renderizado (`.render().await`) para que el tema activo los estilice
|
// propio ciclo de renderizado (`.render().await`) para que el tema activo los estilice igual
|
||||||
// igual (ver `pagetop-bootsier::theme::bs::button`), incluida la traducción de `data-dialog-*`
|
// (ver `pagetop-bootsier::theme::bs::button`), incluida la traducción de `data-dialog-*` que
|
||||||
// que usa el botón de borrado.
|
// usa el botón de borrado.
|
||||||
let permissions_button = Button::anchor(
|
let permissions_button = Button::anchor(
|
||||||
Lc::t("btn-manage-permissions", &LOCALES_USER),
|
Lc::t("btn-manage-permissions", &LOCALES_USER),
|
||||||
permissions_href,
|
permissions_href,
|
||||||
|
|
@ -258,8 +267,8 @@ async fn actions_cell(
|
||||||
.render(cx)
|
.render(cx)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Viaja como query string para que, tanto si el borrado falla como si tiene éxito, la
|
// Viaja como query string para que, tanto si el borrado falla como si tiene éxito, la tabla
|
||||||
// tabla vuelva a mostrarse en la misma página/orden en que estaba, en vez de reiniciarse.
|
// vuelva a mostrarse en la misma página/orden en que estaba, en vez de reiniciarse.
|
||||||
let confirm_href = cx
|
let confirm_href = cx
|
||||||
.route(role_path(id, "delete/confirm"))
|
.route(role_path(id, "delete/confirm"))
|
||||||
.alter_param("sort", sort.as_str())
|
.alter_param("sort", sort.as_str())
|
||||||
|
|
|
||||||
|
|
@ -348,31 +348,33 @@ async fn role_view_details(role: &role::Model, cx: &mut Context) -> Block {
|
||||||
.with_child(table)
|
.with_child(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Un bloque por grupo del catálogo de permisos: cada permiso concedido se marca con la clase
|
// Un bloque por grupo del catálogo con los permisos concedidos al rol; los grupos sin ninguno no se
|
||||||
// `user-admin-permission-granted` (negrita, vía CSS del tema); el resto con
|
// muestran. Si el rol no tiene ningún permiso, un único bloque lo indica.
|
||||||
// `user-admin-permission-missing` (gris claro, vía CSS del tema).
|
|
||||||
fn role_view_permissions(groups: &PermissionGroups) -> Vec<Block> {
|
fn role_view_permissions(groups: &PermissionGroups) -> Vec<Block> {
|
||||||
groups
|
let blocks: Vec<Block> = groups
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|(_, perms)| perms.iter().any(|(_, _, granted)| *granted))
|
||||||
.map(|(group_label, perms)| {
|
.map(|(group_label, perms)| {
|
||||||
let perms = perms.clone();
|
let mut table = Table::new().with_prop(PropsOp::add_classes("user-admin-table"));
|
||||||
|
for (_key, label, _) in perms.iter().filter(|(_, _, granted)| *granted) {
|
||||||
|
table = table.with_row(table::Row::new().with_cell(label.clone()));
|
||||||
|
}
|
||||||
Block::new()
|
Block::new()
|
||||||
.with_title(group_label.clone())
|
.with_title(group_label.clone())
|
||||||
.with_child(Html::with(move |cx| {
|
.with_child(table)
|
||||||
html! {
|
|
||||||
ul class="user-admin-permission-list" {
|
|
||||||
@for (_key, label, granted) in &perms {
|
|
||||||
@if *granted {
|
|
||||||
li class="user-admin-permission-granted" { (label.using(cx)) }
|
|
||||||
} @else {
|
|
||||||
li class="user-admin-permission-missing" { (label.using(cx)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect();
|
||||||
|
|
||||||
|
if blocks.is_empty() {
|
||||||
|
return vec![
|
||||||
|
Block::new()
|
||||||
|
.with_title(Lc::t("title-admin-permissions", &LOCALES_USER))
|
||||||
|
.with_child(
|
||||||
|
Table::new().with_empty(Lc::t("empty-role-permissions", &LOCALES_USER)),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< delete_post >********************************************************************************
|
// **< delete_post >********************************************************************************
|
||||||
|
|
@ -537,8 +539,8 @@ pub(crate) struct RolePermissionsFormData {
|
||||||
|
|
||||||
/// POST /admin/user/roles/{id}/permissions - Reemplaza el conjunto de permisos de un rol.
|
/// POST /admin/user/roles/{id}/permissions - Reemplaza el conjunto de permisos de un rol.
|
||||||
///
|
///
|
||||||
/// Usa `RawForm` + `serde_qs` en lugar de `axum::extract::Form` (basado en `serde_urlencoded`,
|
/// Usa `RawForm` + `serde_qs` en lugar de `axum::extract::Form` (basado en `serde_urlencoded`, que
|
||||||
/// que no deserializa claves repetidas como `permission_keys=a&permission_keys=b` en un `Vec<T>`).
|
/// no deserializa claves repetidas como `permission_keys=a&permission_keys=b` en un `Vec<T>`).
|
||||||
pub(crate) async fn permissions_post(
|
pub(crate) async fn permissions_post(
|
||||||
request: HttpRequest,
|
request: HttpRequest,
|
||||||
web::Path(id): web::Path<i32>,
|
web::Path(id): web::Path<i32>,
|
||||||
|
|
|
||||||
|
|
@ -140,14 +140,16 @@ link-back-to-list = Back to list
|
||||||
|
|
||||||
# **< Admin: confirmations and badges >**
|
# **< Admin: confirmations and badges >**
|
||||||
|
|
||||||
confirm-delete-role = Delete this role? This cannot be undone.
|
confirm-delete-role = Delete this role? This cannot be undone.
|
||||||
confirm-change-status = Change this account's status?
|
confirm-change-status = Change this account's status?
|
||||||
confirm-grant-admin = Grant unrestricted access to this account?
|
confirm-grant-admin = Grant unrestricted access to this account?
|
||||||
confirm-revoke-admin = Revoke this account's unrestricted access?
|
confirm-revoke-admin = Revoke this account's unrestricted access?
|
||||||
badge-system-role = System
|
badge-system-role = System
|
||||||
badge-admin = Administrator
|
badge-admin = Administrator
|
||||||
empty-users-list = No users found.
|
badge-no-permissions = No permissions
|
||||||
empty-roles-list = No roles found.
|
empty-users-list = No users found.
|
||||||
|
empty-roles-list = No roles found.
|
||||||
|
empty-role-permissions = This role has no permissions.
|
||||||
|
|
||||||
# **< Admin: error messages >**
|
# **< Admin: error messages >**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,14 +140,16 @@ link-back-to-list = Volver al listado
|
||||||
|
|
||||||
# **< Administración: confirmaciones y distintivos >**
|
# **< Administración: confirmaciones y distintivos >**
|
||||||
|
|
||||||
confirm-delete-role = ¿Eliminar este rol? Esta acción no se puede deshacer.
|
confirm-delete-role = ¿Eliminar este rol? Esta acción no se puede deshacer.
|
||||||
confirm-change-status = ¿Cambiar el estado de esta cuenta?
|
confirm-change-status = ¿Cambiar el estado de esta cuenta?
|
||||||
confirm-grant-admin = ¿Conceder acceso irrestricto a esta cuenta?
|
confirm-grant-admin = ¿Conceder acceso irrestricto a esta cuenta?
|
||||||
confirm-revoke-admin = ¿Revocar el acceso irrestricto de esta cuenta?
|
confirm-revoke-admin = ¿Revocar el acceso irrestricto de esta cuenta?
|
||||||
badge-system-role = Sistema
|
badge-system-role = Sistema
|
||||||
badge-admin = Administrador
|
badge-admin = Administrador
|
||||||
empty-users-list = No se han encontrado usuarios.
|
badge-no-permissions = Sin permisos
|
||||||
empty-roles-list = No se han encontrado roles.
|
empty-users-list = No se han encontrado usuarios.
|
||||||
|
empty-roles-list = No se han encontrado roles.
|
||||||
|
empty-role-permissions = Este rol no tiene permisos.
|
||||||
|
|
||||||
# **< Administración: mensajes de error >**
|
# **< Administración: mensajes de error >**
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Servicio de administración de roles: listado, CRUD y permisos.
|
//! Servicio de administración de roles: listado, CRUD y permisos.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
use pagetop_seaorm::db::{
|
use pagetop_seaorm::db::{
|
||||||
|
|
@ -47,6 +47,7 @@ pub(crate) struct RoleListItem {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub locked: bool,
|
pub locked: bool,
|
||||||
pub user_count: u64,
|
pub user_count: u64,
|
||||||
|
pub has_permissions: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct RoleListParams {
|
pub(crate) struct RoleListParams {
|
||||||
|
|
@ -104,7 +105,7 @@ pub(crate) async fn list_roles_page(
|
||||||
async fn role_items(roles: Vec<role::Model>) -> Result<Vec<RoleListItem>, AuthError> {
|
async fn role_items(roles: Vec<role::Model>) -> Result<Vec<RoleListItem>, AuthError> {
|
||||||
let role_ids: Vec<i32> = roles.iter().map(|r| r.id).collect();
|
let role_ids: Vec<i32> = roles.iter().map(|r| r.id).collect();
|
||||||
let counts: Vec<(i32, i64)> = user_role::Entity::find()
|
let counts: Vec<(i32, i64)> = user_role::Entity::find()
|
||||||
.filter(user_role::Column::RoleId.is_in(role_ids))
|
.filter(user_role::Column::RoleId.is_in(role_ids.clone()))
|
||||||
.select_only()
|
.select_only()
|
||||||
.column(user_role::Column::RoleId)
|
.column(user_role::Column::RoleId)
|
||||||
.column_as(user_role::Column::RoleId.count(), "count")
|
.column_as(user_role::Column::RoleId.count(), "count")
|
||||||
|
|
@ -116,11 +117,22 @@ async fn role_items(roles: Vec<role::Model>) -> Result<Vec<RoleListItem>, AuthEr
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(role_id, count)| (role_id, count as u64))
|
.map(|(role_id, count)| (role_id, count as u64))
|
||||||
.collect();
|
.collect();
|
||||||
|
let with_permissions: HashSet<i32> = role_permission::Entity::find()
|
||||||
|
.filter(role_permission::Column::RoleId.is_in(role_ids))
|
||||||
|
.select_only()
|
||||||
|
.column(role_permission::Column::RoleId)
|
||||||
|
.distinct()
|
||||||
|
.into_tuple()
|
||||||
|
.all(dbconn())
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
Ok(roles
|
Ok(roles
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|role| RoleListItem {
|
.map(|role| RoleListItem {
|
||||||
user_count: counts_by_role.get(&role.id).copied().unwrap_or(0),
|
user_count: counts_by_role.get(&role.id).copied().unwrap_or(0),
|
||||||
|
has_permissions: with_permissions.contains(&role.id),
|
||||||
id: role.id,
|
id: role.id,
|
||||||
machine_name: role.machine_name,
|
machine_name: role.machine_name,
|
||||||
label: role.label,
|
label: role.label,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue