feat(sidebar) : section Administration + groupe Mon compte + gate de section
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -6,21 +6,46 @@ declare(strict_types=1);
|
|||||||
* Sidebar configuration.
|
* Sidebar configuration.
|
||||||
*
|
*
|
||||||
* This file defines the sidebar sections displayed in the frontend.
|
* 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.
|
* Each SECTION may declare :
|
||||||
* Items may also declare a `permission` key (RBAC permission code) : the item
|
* - `label` (required) : i18n key resolved by the frontend
|
||||||
* is hidden from users who do not hold that permission.
|
* - `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
|
* This config is decoupled from the modules themselves: you can freely
|
||||||
* move an item from one section to another without touching the module code.
|
* 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 [
|
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',
|
'label' => 'sidebar.administration.section',
|
||||||
'icon' => 'mdi:view-dashboard-outline',
|
'icon' => 'mdi:cog-outline',
|
||||||
'items' => [
|
'items' => [
|
||||||
[
|
[
|
||||||
'label' => 'sidebar.general.dashboard',
|
'label' => 'sidebar.general.dashboard',
|
||||||
@@ -28,12 +53,6 @@ return [
|
|||||||
'icon' => 'mdi:view-dashboard-outline',
|
'icon' => 'mdi:view-dashboard-outline',
|
||||||
'module' => 'core',
|
'module' => 'core',
|
||||||
],
|
],
|
||||||
[
|
|
||||||
'label' => 'sidebar.general.admin',
|
|
||||||
'to' => '/admin',
|
|
||||||
'icon' => 'mdi:cog-outline',
|
|
||||||
'module' => 'core',
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
'label' => 'sidebar.core.roles',
|
'label' => 'sidebar.core.roles',
|
||||||
'to' => '/admin/roles',
|
'to' => '/admin/roles',
|
||||||
@@ -62,12 +81,6 @@ return [
|
|||||||
'module' => 'core',
|
'module' => 'core',
|
||||||
'permission' => 'core.audit_log.view',
|
'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',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -13,12 +13,16 @@
|
|||||||
"actions": "Actions"
|
"actions": "Actions"
|
||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"general": {
|
"administration": {
|
||||||
"section": "Général",
|
"section": "Administration"
|
||||||
"dashboard": "Tableau de bord",
|
},
|
||||||
"admin": "Administration",
|
"account": {
|
||||||
|
"section": "Mon compte",
|
||||||
"logout": "Déconnexion"
|
"logout": "Déconnexion"
|
||||||
},
|
},
|
||||||
|
"general": {
|
||||||
|
"dashboard": "Tableau de bord"
|
||||||
|
},
|
||||||
"commercial": {
|
"commercial": {
|
||||||
"section": "Commercial",
|
"section": "Commercial",
|
||||||
"suppliers": "Répertoire fournisseurs"
|
"suppliers": "Répertoire fournisseurs"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class SidebarProvider implements ProviderInterface
|
|||||||
/** @var list<string> */
|
/** @var list<string> */
|
||||||
private readonly array $activeModuleIds;
|
private readonly array $activeModuleIds;
|
||||||
|
|
||||||
/** @var list<array{label: string, icon: string, items: list<array{label: string, to: string, icon: string, module: string, permission?: string}>}> */
|
/** @var list<array{label: string, icon: string, permission?: string, items: list<array{label: string, to: string, icon: string, module: string, permission?: string}>}> */
|
||||||
private readonly array $sidebarConfig;
|
private readonly array $sidebarConfig;
|
||||||
|
|
||||||
public function __construct(private readonly Security $security)
|
public function __construct(private readonly Security $security)
|
||||||
@@ -47,6 +47,23 @@ class SidebarProvider implements ProviderInterface
|
|||||||
$disabledRoutes = [];
|
$disabledRoutes = [];
|
||||||
|
|
||||||
foreach ($this->sidebarConfig as $section) {
|
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 = [];
|
$items = [];
|
||||||
foreach ($section['items'] ?? [] as $item) {
|
foreach ($section['items'] ?? [] as $item) {
|
||||||
$isActive = in_array($item['module'] ?? null, $this->activeModuleIds, true);
|
$isActive = in_array($item['module'] ?? null, $this->activeModuleIds, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user