From 701a480442d8a2f59ecef4a9ade530988142cd2e Mon Sep 17 00:00:00 2001 From: Matthieu Date: Wed, 22 Apr 2026 11:28:44 +0200 Subject: [PATCH] feat(sidebar) : section Administration + groupe Mon compte + gate de section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Section "Général" renommée en "Administration" (label i18n sidebar.administration.section). - Item "Administration" (/admin) retiré : la route n'existait pas cote front, generait un 404 Nuxt silencieux a chaque clic. - "Deconnexion" sortie de la section admin, deplacee dans une nouvelle section "Mon compte" (sidebar.account.section) sans permission RBAC — accessible a tout user authentifie. - SidebarProvider supporte desormais un champ `permission` au niveau section : umbrella gate qui masque toute la section et bascule toutes ses routes dans disabledRoutes. Voir doc inline dans config/sidebar.php pour le pattern d'usage. Avantage : pour gater toute l'administration derriere une permission coarse (ex: 'core.admin.access' future), ajouter 'permission' => 'core.admin.access' sur la section suffit — pas besoin de dupliquer la permission sur chaque item. Co-Authored-By: Claude Opus 4.7 (1M context) --- config/sidebar.php | 68 +++++++++++++------ frontend/i18n/locales/fr.json | 12 ++-- .../ApiPlatform/State/SidebarProvider.php | 19 +++++- 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/config/sidebar.php b/config/sidebar.php index 4c14db7..9119b48 100644 --- a/config/sidebar.php +++ b/config/sidebar.php @@ -6,21 +6,46 @@ declare(strict_types=1); * Sidebar configuration. * * This file defines the sidebar sections displayed in the frontend. - * Each item references the module that owns it via the `module` key. - * Items whose module is not active (see config/modules.php) are filtered out. - * Items may also declare a `permission` key (RBAC permission code) : the item - * is hidden from users who do not hold that permission. + * + * Each SECTION may declare : + * - `label` (required) : i18n key resolved by the frontend + * - `icon` (required) : MDI icon name + * - `items` (required) : list of items (see below) + * - `permission` (opt.) : RBAC permission code ; when set, the whole + * section (and every one of its items) is hidden + * from users who do not hold that permission. + * Use this for "umbrella" sections like + * Administration where you want to gate the + * entire group behind one coarse permission. + * + * Each ITEM may declare : + * - `label` (required) : i18n key + * - `to` (required) : Nuxt route + * - `icon` (required) : MDI icon name + * - `module` (required) : owner module ID ; item is hidden if the + * module is not listed in config/modules.php + * - `permission` (opt.) : RBAC permission code ; finer-grained gate + * applied in addition to the section-level one + * + * Precedence : section-level `permission` is evaluated first. If it fails, + * the whole section is skipped and every item's `to` is added to the + * `disabledRoutes` payload of /api/sidebar (so the front middleware can + * redirect any direct navigation). Individual items without their own + * permission are implicitly protected by the section-level one. * * This config is decoupled from the modules themselves: you can freely * move an item from one section to another without touching the module code. - * - * Label keys are i18n keys resolved by the frontend (see frontend/i18n/locales/). */ return [ + // Section "Administration" : regroupe toutes les pages de configuration + // applicative (RBAC, users, sites, audit log). Gate implicite : si l'user + // n'a aucune des permissions item, la section se vide et disparait. + // Pour imposer un gate explicite (ex: "seuls les membres du groupe support + // voient l'administration"), ajouter ici : 'permission' => 'core.admin.access'. [ - 'label' => 'sidebar.general.section', - 'icon' => 'mdi:view-dashboard-outline', + 'label' => 'sidebar.administration.section', + 'icon' => 'mdi:cog-outline', 'items' => [ [ 'label' => 'sidebar.general.dashboard', @@ -28,12 +53,6 @@ return [ 'icon' => 'mdi:view-dashboard-outline', 'module' => 'core', ], - [ - 'label' => 'sidebar.general.admin', - 'to' => '/admin', - 'icon' => 'mdi:cog-outline', - 'module' => 'core', - ], [ 'label' => 'sidebar.core.roles', 'to' => '/admin/roles', @@ -62,12 +81,6 @@ return [ 'module' => 'core', 'permission' => 'core.audit_log.view', ], - [ - 'label' => 'sidebar.general.logout', - 'to' => '/logout', - 'icon' => 'mdi:logout', - 'module' => 'core', - ], ], ], [ @@ -82,4 +95,19 @@ return [ ], ], ], + // Section "Mon compte" : espace personnel. Accessible a tout user authentifie + // (aucune permission RBAC requise, tous les items restent dans `core` pour + // rester toujours presents meme quand les modules metier sont desactives). + [ + 'label' => 'sidebar.account.section', + 'icon' => 'mdi:account-circle-outline', + 'items' => [ + [ + 'label' => 'sidebar.account.logout', + 'to' => '/logout', + 'icon' => 'mdi:logout', + 'module' => 'core', + ], + ], + ], ]; diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index f85270b..6dbacb0 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -13,12 +13,16 @@ "actions": "Actions" }, "sidebar": { - "general": { - "section": "Général", - "dashboard": "Tableau de bord", - "admin": "Administration", + "administration": { + "section": "Administration" + }, + "account": { + "section": "Mon compte", "logout": "Déconnexion" }, + "general": { + "dashboard": "Tableau de bord" + }, "commercial": { "section": "Commercial", "suppliers": "Répertoire fournisseurs" diff --git a/src/Shared/Infrastructure/ApiPlatform/State/SidebarProvider.php b/src/Shared/Infrastructure/ApiPlatform/State/SidebarProvider.php index 8600ae9..4ba9392 100644 --- a/src/Shared/Infrastructure/ApiPlatform/State/SidebarProvider.php +++ b/src/Shared/Infrastructure/ApiPlatform/State/SidebarProvider.php @@ -17,7 +17,7 @@ class SidebarProvider implements ProviderInterface /** @var list */ private readonly array $activeModuleIds; - /** @var list}> */ + /** @var list}> */ private readonly array $sidebarConfig; public function __construct(private readonly Security $security) @@ -47,6 +47,23 @@ class SidebarProvider implements ProviderInterface $disabledRoutes = []; foreach ($this->sidebarConfig as $section) { + // Gate de section (optionnel) : si la section declare une permission + // et que l'utilisateur ne la possede pas, la section entiere est + // masquee. Toutes les routes de ses items basculent dans + // `disabledRoutes` pour que le middleware front redirige toute + // navigation directe, y compris si l'item n'a pas de permission + // individuelle (la section agit comme un umbrella gate). + $sectionPermission = $section['permission'] ?? null; + if (null !== $sectionPermission && !$this->security->isGranted($sectionPermission)) { + foreach ($section['items'] ?? [] as $item) { + if (isset($item['to'])) { + $disabledRoutes[] = $item['to']; + } + } + + continue; + } + $items = []; foreach ($section['items'] ?? [] as $item) { $isActive = in_array($item['module'] ?? null, $this->activeModuleIds, true);