feat: Optimisations UX/UI et amélioration import/export CSV

Optimisations majeures de l'interface utilisateur et amélioration du système d'import/export CSV avec rapport d'erreurs détaillé.

## Optimisations UX/UI
- Suppression des blocs Actions Rapides redondants dans les pages list/view
- Consolidation des actions dans les en-têtes de page
- Conversion des filtres en panneau collapsible avec badge Filtres actifs
- Suppression du sous-menu Attribution Rôles (redondant avec /users/edit)
- Amélioration de la navigation et de l'ergonomie générale
- Correction des attributs iconLeft non supportés par fr:fieldInput

## Import/Export CSV
- Ajout de ImportResultDTO avec rapport détaillé des erreurs
- Création de CsvValidationHelper pour validation robuste des données
- Amélioration des messages d'erreur avec numéros de ligne
- Support de colonnes flexibles (username,prenom,nom,email)
- Validation stricte des formats email

## Corrections techniques
- Fix DashboardBeanTest: getRecentActions() → getActionsLast24h()
- Fix UserServiceImplTest: retour ImportResultDTO au lieu de int
- Amélioration de la gestion d'erreurs dans AuditServiceImpl
- Migration Flyway V1.0.0 pour la table audit_logs

## Infrastructure
- Mise à jour .gitignore professionnel (exclusion docs de session)
- Configuration production sécurisée (variables d'environnement)
- Pas de secrets hardcodés dans les fichiers de configuration

Testé et validé en environnement de développement.
This commit is contained in:
lionsdev
2026-01-03 13:53:35 +00:00
parent 564d29a9d2
commit c5cbe61002
22 changed files with 1697 additions and 1536 deletions

View File

@@ -190,6 +190,23 @@
<redirect />
</navigation-case>
<!-- ================================================================
FREYA EXTENSION SHOWCASE
================================================================ -->
<navigation-case>
<description>Page de démonstration complète Freya Extension</description>
<from-outcome>freyaShowcasePage</from-outcome>
<to-view-id>/pages/user-manager/freya-showcase.xhtml</to-view-id>
<redirect />
</navigation-case>
<navigation-case>
<description>Navigation directe vers Freya Showcase</description>
<from-outcome>/pages/user-manager/freya-showcase</from-outcome>
<to-view-id>/pages/user-manager/freya-showcase.xhtml</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
</faces-config>

View File

@@ -4,6 +4,7 @@
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya"
template="/templates/main-template.xhtml">
<ui:define name="title">Affectation des Realms - Lions User Manager</ui:define>
@@ -24,11 +25,11 @@
<p class="text-600 m-0">Gérer les permissions d'administration par realm (contrôle multi-tenant)</p>
</div>
</div>
<p:commandButton value="Nouvelle Affectation"
icon="pi pi-plus"
styleClass="p-button-success"
onclick="PF('assignRealmDialog').show();"
type="button" />
<fr:commandButton value="Nouvelle Affectation"
icon="pi pi-plus"
severity="success"
onclick="PF('assignRealmDialog').show();"
type="button" />
</div>
</div>
</div>
@@ -91,30 +92,32 @@
<div class="card">
<div class="flex align-items-center justify-content-between mb-4">
<h5 class="m-0">Affectations Actuelles</h5>
<p:commandButton value="Rafraîchir"
icon="pi pi-refresh"
styleClass="p-button-outlined p-button-sm"
action="#{realmAssignmentBean.loadAssignments}"
update=":formRealmAssignments" />
<fr:commandButton value="Rafraîchir"
icon="pi pi-refresh"
outlined="true"
size="small"
action="#{realmAssignmentBean.loadAssignments}"
update=":formRealmAssignments" />
</div>
<p:messages id="messages" showDetail="true" closable="true">
<fr:message id="messages" showDetail="true" closable="true">
<p:autoUpdate />
</p:messages>
</fr:message>
<p:dataTable id="assignmentsTable"
value="#{realmAssignmentBean.assignments}"
var="assignment"
paginator="true"
rows="10"
rows="25"
paginatorPosition="bottom"
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
rowsPerPageTemplate="10,20,50"
rowsPerPageTemplate="10,25,50,100"
emptyMessage="Aucune affectation configurée"
responsiveLayout="scroll"
styleClass="p-datatable-sm">
<!-- Colonne Utilisateur -->
<p:column headerText="Utilisateur" sortBy="#{assignment.username}" filterBy="#{assignment.username}" filterMatchMode="contains">
<p:column headerText="Utilisateur" sortBy="#{assignment.username}" filterBy="#{assignment.username}" filterMatchMode="contains" priority="1">
<div class="flex align-items-center gap-2">
<div style="width: 32px; height: 32px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-color), var(--primary-600)); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: bold; color: white;">
<h:outputText value="#{assignment.username != null and assignment.username.length() >= 2 ? assignment.username.substring(0,2).toUpperCase() : 'U'}" />
@@ -127,92 +130,101 @@
</p:column>
<!-- Colonne Realm -->
<p:column headerText="Realm" sortBy="#{assignment.realmName}" filterBy="#{assignment.realmName}" filterMatchMode="contains">
<p:tag value="#{assignment.realmName}"
severity="info"
icon="pi pi-globe" />
<p:column headerText="Realm" sortBy="#{assignment.realmName}" filterBy="#{assignment.realmName}" filterMatchMode="contains" priority="2">
<fr:tag value="#{assignment.realmName}"
severity="info"
icon="pi pi-globe" />
</p:column>
<!-- Colonne Type -->
<p:column headerText="Type" style="width: 150px">
<p:tag value="Super Admin"
severity="danger"
icon="pi pi-star"
rendered="#{assignment.isSuperAdmin()}" />
<p:tag value="Realm Admin"
severity="success"
icon="pi pi-shield"
rendered="#{!assignment.isSuperAdmin()}" />
<p:column headerText="Type" style="width: 150px" priority="3">
<fr:tag value="Super Admin"
severity="danger"
icon="pi pi-star"
rendered="#{assignment.isSuperAdmin()}" />
<fr:tag value="Realm Admin"
severity="success"
icon="pi pi-shield"
rendered="#{!assignment.isSuperAdmin()}" />
</p:column>
<!-- Colonne Statut -->
<p:column headerText="Statut" style="width: 120px">
<p:tag value="Actif"
severity="success"
icon="pi pi-check-circle"
rendered="#{assignment.active and !assignment.isExpired()}" />
<p:tag value="Inactif"
severity="warning"
icon="pi pi-times-circle"
rendered="#{!assignment.active}" />
<p:tag value="Expiré"
severity="danger"
icon="pi pi-exclamation-circle"
rendered="#{assignment.isExpired()}" />
<p:column headerText="Statut" style="width: 120px" priority="4">
<fr:tag value="Actif"
severity="success"
icon="pi pi-check-circle"
rendered="#{assignment.active and !assignment.isExpired()}" />
<fr:tag value="Inactif"
severity="warning"
icon="pi pi-times-circle"
rendered="#{!assignment.active}" />
<fr:tag value="Expiré"
severity="danger"
icon="pi pi-exclamation-circle"
rendered="#{assignment.isExpired()}" />
</p:column>
<!-- Colonne Assigné le -->
<p:column headerText="Assigné le" sortBy="#{assignment.assignedAt}" style="width: 180px">
<p:column headerText="Assigné le" sortBy="#{assignment.assignedAt}" style="width: 180px" priority="5">
<h:outputText value="#{assignment.assignedAt}">
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" />
</h:outputText>
</p:column>
<!-- Colonne Par -->
<p:column headerText="Par" sortBy="#{assignment.assignedBy}" style="width: 150px">
<p:column headerText="Par" sortBy="#{assignment.assignedBy}" style="width: 150px" priority="6">
<h:outputText value="#{assignment.assignedBy}" />
</p:column>
<!-- Colonne Actions -->
<p:column headerText="Actions" style="width: 120px; text-align: center">
<p:column headerText="Actions" style="width: 120px; text-align: center" priority="1">
<div class="flex gap-1 justify-content-center flex-wrap">
<!-- Bouton Désactiver -->
<p:commandButton icon="pi pi-ban"
styleClass="p-button-rounded p-button-text p-button-sm p-button-warning"
title="Désactiver"
action="#{realmAssignmentBean.deactivateAssignment(assignment)}"
update=":formRealmAssignments"
process="@this"
rendered="#{assignment.active}">
<fr:commandButton icon="pi pi-ban"
rounded="true"
text="true"
size="small"
severity="warning"
title="Désactiver"
action="#{realmAssignmentBean.deactivateAssignment(assignment)}"
update=":formRealmAssignments"
process="@this"
rendered="#{assignment.active}">
<p:confirm header="Confirmation"
message="Désactiver cette affectation ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</fr:commandButton>
<!-- Bouton Activer -->
<p:commandButton icon="pi pi-check"
styleClass="p-button-rounded p-button-text p-button-sm p-button-success"
title="Activer"
action="#{realmAssignmentBean.activateAssignment(assignment)}"
update=":formRealmAssignments"
process="@this"
rendered="#{!assignment.active}">
<fr:commandButton icon="pi pi-check"
rounded="true"
text="true"
size="small"
severity="success"
title="Activer"
action="#{realmAssignmentBean.activateAssignment(assignment)}"
update=":formRealmAssignments"
process="@this"
rendered="#{!assignment.active}">
<p:confirm header="Confirmation"
message="Activer cette affectation ?"
icon="pi pi-question-circle" />
</p:commandButton>
</fr:commandButton>
<!-- Bouton Supprimer -->
<p:commandButton icon="pi pi-trash"
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
title="Supprimer"
action="#{realmAssignmentBean.revokeAssignment(assignment)}"
update=":formRealmAssignments"
process="@this">
<fr:commandButton icon="pi pi-trash"
rounded="true"
text="true"
size="small"
severity="danger"
title="Supprimer"
action="#{realmAssignmentBean.revokeAssignment(assignment)}"
update=":formRealmAssignments"
process="@this">
<p:confirm header="Confirmation"
message="Révoquer l'accès de #{assignment.username} au realm #{assignment.realmName} ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</fr:commandButton>
</div>
</p:column>
</p:dataTable>
@@ -234,72 +246,59 @@
<h:form id="formAssignRealm">
<div class="grid">
<div class="col-12">
<label class="block text-900 font-semibold mb-2">
<i class="pi pi-user text-primary mr-1"></i>
Utilisateur *
</label>
<p:selectOneMenu value="#{realmAssignmentBean.selectedUserId}"
styleClass="w-full"
<fr:fieldSelect id="userId"
label="Utilisateur *"
value="#{realmAssignmentBean.selectedUserId}"
filter="true"
filterMatchMode="contains">
filterMatchMode="contains"
iconLeft="pi pi-user">
<f:selectItem itemLabel="Sélectionner un utilisateur" itemValue="" noSelectionOption="true" />
<f:selectItems value="#{realmAssignmentBean.availableUsers}"
var="user"
itemValue="#{user.id}"
itemLabel="#{user.username} (#{user.email})" />
</p:selectOneMenu>
</fr:fieldSelect>
</div>
<div class="col-12">
<label class="block text-900 font-semibold mb-2">
<i class="pi pi-globe text-primary mr-1"></i>
Realm *
</label>
<p:selectOneMenu value="#{realmAssignmentBean.selectedRealmName}"
styleClass="w-full">
<fr:fieldSelect id="realmName"
label="Realm *"
value="#{realmAssignmentBean.selectedRealmName}"
iconLeft="pi pi-globe">
<f:selectItem itemLabel="Sélectionner un realm" itemValue="" noSelectionOption="true" />
<f:selectItems value="#{realmAssignmentBean.availableRealms}" />
</p:selectOneMenu>
</fr:fieldSelect>
</div>
<div class="col-12">
<label class="block text-900 font-semibold mb-2">
<i class="pi pi-comment text-primary mr-1"></i>
Raison
</label>
<p:inputText value="#{realmAssignmentBean.newAssignment.raison}"
styleClass="w-full"
placeholder="Ex: Nouveau gestionnaire du realm client" />
<fr:fieldInput id="raison"
label="Raison"
value="#{realmAssignmentBean.newAssignment.raison}"
placeholder="Ex: Nouveau gestionnaire du realm client"
iconLeft="pi pi-comment" />
</div>
<div class="col-12">
<label class="block text-900 font-semibold mb-2">
<i class="pi pi-file-edit text-primary mr-1"></i>
Commentaires
</label>
<p:inputTextarea value="#{realmAssignmentBean.newAssignment.commentaires}"
rows="3"
styleClass="w-full"
placeholder="Commentaires administratifs (optionnel)" />
<fr:fieldTextarea id="commentaires"
label="Commentaires"
value="#{realmAssignmentBean.newAssignment.commentaires}"
rows="3"
placeholder="Commentaires administratifs (optionnel)"
iconLeft="pi pi-file-edit" />
</div>
<div class="col-12">
<div class="flex align-items-center">
<p:selectBooleanCheckbox value="#{realmAssignmentBean.newAssignment.temporaire}"
itemLabel="Affectation temporaire"
styleClass="mr-2" />
</div>
<fr:fieldCheckbox id="temporaire"
label="Affectation temporaire"
value="#{realmAssignmentBean.newAssignment.temporaire}" />
</div>
<div class="col-12" rendered="#{realmAssignmentBean.newAssignment.temporaire}">
<label class="block text-900 font-semibold mb-2">
<i class="pi pi-calendar text-primary mr-1"></i>
Date d'expiration
</label>
<p:calendar value="#{realmAssignmentBean.newAssignment.dateExpiration}"
pattern="dd/MM/yyyy HH:mm"
showTime="true"
styleClass="w-full" />
<fr:fieldCalendar id="dateExpiration"
label="Date d'expiration"
value="#{realmAssignmentBean.newAssignment.dateExpiration}"
pattern="dd/MM/yyyy HH:mm"
showTime="true" />
</div>
<div class="col-12">
@@ -319,18 +318,20 @@
<div class="col-12">
<div class="flex gap-2">
<p:commandButton value="Annuler"
icon="pi pi-times"
styleClass="p-button-text flex-1"
onclick="PF('assignRealmDialog').hide();"
type="button"
action="#{realmAssignmentBean.resetForm}" />
<p:commandButton value="Assigner"
icon="pi pi-check"
styleClass="p-button-success flex-1"
action="#{realmAssignmentBean.assignRealm}"
update=":formRealmAssignments :formAssignRealm"
oncomplete="if (args.validationFailed == false) PF('assignRealmDialog').hide();" />
<fr:commandButton value="Annuler"
icon="pi pi-times"
text="true"
styleClass="flex-1"
onclick="PF('assignRealmDialog').hide();"
type="button"
action="#{realmAssignmentBean.resetForm}" />
<fr:commandButton value="Assigner"
icon="pi pi-check"
severity="success"
styleClass="flex-1"
action="#{realmAssignmentBean.assignRealm}"
update=":formRealmAssignments :formAssignRealm"
oncomplete="if (args.validationFailed == false) PF('assignRealmDialog').hide();" />
</div>
</div>
</div>
@@ -338,11 +339,12 @@
</p:dialog>
<!-- ================================================================
DIALOG DE CONFIRMATION
DIALOG DE CONFIRMATION (Freya Extension)
================================================================ -->
<!-- Le confirmDialog est géré par p:confirm dans les boutons d'action -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade" responsive="true" width="400">
<p:commandButton value="Non" type="button" styleClass="p-button-text" icon="pi pi-times" />
<p:commandButton value="Oui" type="button" styleClass="p-button-danger" icon="pi pi-check" />
<fr:commandButton value="Non" type="button" text="true" icon="pi pi-times" />
<fr:commandButton value="Oui" type="button" severity="danger" icon="pi pi-check" />
</p:confirmDialog>
</ui:define>

View File

@@ -4,6 +4,7 @@
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya"
template="/templates/main-template.xhtml">
<ui:param name="page" value="#{auditConsultationBean}"/>
@@ -25,12 +26,11 @@
</div>
</div>
<h:form id="formHeaderActions">
<p:commandButton
value="Exporter CSV"
icon="pi pi-download"
styleClass="p-button-success"
action="#{auditConsultationBean.exportToCSV}"
ajax="false" />
<fr:commandButton value="Exporter CSV"
icon="pi pi-download"
severity="success"
action="#{auditConsultationBean.exportToCSV}"
ajax="false" />
</h:form>
</div>
</div>
@@ -144,74 +144,70 @@
<h:form id="formFilters">
<div class="grid">
<div class="col-12 md:col-6 lg:col-4">
<label for="acteurFilter" class="block text-900 font-medium mb-2">Acteur</label>
<p:inputText id="acteurFilter"
value="#{auditConsultationBean.acteurUsername}"
placeholder="Nom d'utilisateur..."
styleClass="w-full" />
<fr:fieldInput id="acteurFilter"
label="Acteur"
value="#{auditConsultationBean.acteurUsername}"
placeholder="Nom d'utilisateur..."
iconLeft="pi pi-user" />
</div>
<div class="col-12 md:col-6 lg:col-4">
<label for="typeActionFilter" class="block text-900 font-medium mb-2">Type d'action</label>
<p:selectOneMenu id="typeActionFilter"
<fr:fieldSelect id="typeActionFilter"
label="Type d'action"
value="#{auditConsultationBean.selectedTypeAction}"
styleClass="w-full">
iconLeft="pi pi-bolt">
<f:selectItem itemLabel="Tous les types" itemValue="" />
<f:selectItems value="#{auditConsultationBean.typeActionOptions}" />
</p:selectOneMenu>
</fr:fieldSelect>
</div>
<div class="col-12 md:col-6 lg:col-4">
<label for="succesFilter" class="block text-900 font-medium mb-2">Résultat</label>
<p:selectOneMenu id="succesFilter"
<fr:fieldSelect id="succesFilter"
label="Résultat"
value="#{auditConsultationBean.succes}"
styleClass="w-full">
iconLeft="pi pi-check-circle">
<f:selectItem itemLabel="Tous" itemValue="" />
<f:selectItem itemLabel="Succès" itemValue="true" />
<f:selectItem itemLabel="Échec" itemValue="false" />
</p:selectOneMenu>
</fr:fieldSelect>
</div>
<div class="col-12 md:col-6 lg:col-4">
<label for="dateDebutFilter" class="block text-900 font-medium mb-2">Date début</label>
<p:calendar id="dateDebutFilter"
value="#{auditConsultationBean.dateDebut}"
pattern="dd/MM/yyyy"
showIcon="true"
styleClass="w-full" />
<fr:fieldCalendar id="dateDebutFilter"
label="Date début"
value="#{auditConsultationBean.dateDebut}"
pattern="dd/MM/yyyy"
showIcon="true" />
</div>
<div class="col-12 md:col-6 lg:col-4">
<label for="dateFinFilter" class="block text-900 font-medium mb-2">Date fin</label>
<p:calendar id="dateFinFilter"
value="#{auditConsultationBean.dateFin}"
pattern="dd/MM/yyyy"
showIcon="true"
styleClass="w-full" />
<fr:fieldCalendar id="dateFinFilter"
label="Date fin"
value="#{auditConsultationBean.dateFin}"
pattern="dd/MM/yyyy"
showIcon="true" />
</div>
<div class="col-12 md:col-6 lg:col-4">
<label for="ressourceFilter" class="block text-900 font-medium mb-2">Type ressource</label>
<p:inputText id="ressourceFilter"
value="#{auditConsultationBean.ressourceType}"
placeholder="USER, ROLE, CLIENT..."
styleClass="w-full" />
<fr:fieldInput id="ressourceFilter"
label="Type ressource"
value="#{auditConsultationBean.ressourceType}"
placeholder="USER, ROLE, CLIENT..."
iconLeft="pi pi-database" />
</div>
</div>
<div class="flex gap-2 justify-content-end mt-4">
<p:commandButton
value="Rechercher"
icon="pi pi-search"
styleClass="p-button-primary"
action="#{auditConsultationBean.searchLogs}"
update=":formAuditLogs:auditLogsTable" />
<p:commandButton
value="Réinitialiser"
icon="pi pi-refresh"
styleClass="p-button-secondary"
action="#{auditConsultationBean.resetFilters}"
update=":formAuditLogs:auditLogsTable @form" />
<fr:commandButton value="Rechercher"
icon="pi pi-search"
severity="primary"
action="#{auditConsultationBean.searchLogs}"
update=":formAuditLogs:auditLogsTable" />
<fr:commandButton value="Réinitialiser"
icon="pi pi-refresh"
severity="secondary"
action="#{auditConsultationBean.resetFilters}"
update=":formAuditLogs:auditLogsTable @form" />
</div>
</h:form>
</div>
@@ -227,9 +223,9 @@
<i class="pi pi-list text-blue-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Logs d'Audit</h5>
</div>
<p:tag value="#{auditConsultationBean.totalRecords} log(s)"
severity="info"
icon="pi pi-history" />
<fr:tag value="#{auditConsultationBean.totalRecords} log(s)"
severity="info"
icon="pi pi-history" />
</div>
<h:form id="formAuditLogs">
@@ -245,16 +241,16 @@
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
styleClass="w-full"
emptyMessage="Aucun log d'audit trouvé"
reflow="true">
responsiveLayout="scroll">
<!-- Colonne Statut -->
<p:column headerText="Statut" style="width: 100px; text-align: center">
<p:tag value="#{log.succes ? 'Succès' : 'Échec'}"
severity="#{log.succes ? 'success' : 'danger'}" />
<p:column headerText="Statut" style="width: 100px; text-align: center" priority="2">
<fr:tag value="#{log.succes ? 'Succès' : 'Échec'}"
severity="#{log.succes ? 'success' : 'danger'}" />
</p:column>
<!-- Colonne Type d'action -->
<p:column headerText="Type d'action" sortBy="#{log.typeAction}" style="width: 180px">
<p:column headerText="Type d'action" sortBy="#{log.typeAction}" style="width: 180px" priority="1">
<div class="flex align-items-center gap-2">
<i class="pi pi-bolt text-orange-500"></i>
<span class="font-semibold text-900">#{log.typeAction}</span>
@@ -262,7 +258,7 @@
</p:column>
<!-- Colonne Acteur -->
<p:column headerText="Acteur" sortBy="#{log.acteurUsername}" style="width: 200px">
<p:column headerText="Acteur" sortBy="#{log.acteurUsername}" style="width: 200px" priority="3">
<div class="flex align-items-center gap-2">
<div class="border-circle bg-primary text-white flex align-items-center justify-content-center"
style="width: 32px; height: 32px; flex-shrink: 0; font-size: 0.75rem;">
@@ -275,7 +271,7 @@
</p:column>
<!-- Colonne Ressource -->
<p:column headerText="Ressource" style="width: 150px">
<p:column headerText="Ressource" style="width: 150px" priority="5">
<div class="flex align-items-center gap-2">
<i class="pi pi-database text-blue-500"></i>
<span class="text-900">#{log.ressourceType}</span>
@@ -283,7 +279,7 @@
</p:column>
<!-- Colonne Date -->
<p:column headerText="Date" sortBy="#{log.dateAction}" style="width: 180px">
<p:column headerText="Date" sortBy="#{log.dateAction}" style="width: 180px" priority="4">
<div class="flex align-items-center gap-2">
<i class="pi pi-calendar text-purple-500"></i>
<span class="text-900">#{log.dateAction}</span>
@@ -291,28 +287,30 @@
</p:column>
<!-- Colonne Détails -->
<p:column headerText="Détails" style="width: 250px">
<p:column headerText="Détails" style="width: 250px" priority="6">
<span class="text-600 text-sm" style="display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
#{not empty log.details ? log.details : '-'}
</span>
</p:column>
<!-- Colonne IP -->
<p:column headerText="Adresse IP" style="width: 130px">
<p:column headerText="Adresse IP" style="width: 130px" priority="6">
<span class="text-600 text-sm font-mono">
#{not empty log.adresseIp ? log.adresseIp : '-'}
</span>
</p:column>
<!-- Colonne Actions -->
<p:column headerText="Actions" style="width: 80px; text-align: center">
<p:commandButton
icon="pi pi-eye"
styleClass="p-button-rounded p-button-text p-button-sm p-button-info"
title="Voir les détails"
onclick="PF('auditLogDetailsDialog').show()">
<p:column headerText="Actions" style="width: 80px; text-align: center" priority="1">
<fr:commandButton icon="pi pi-eye"
rounded="true"
text="true"
size="small"
severity="info"
title="Voir les détails"
onclick="PF('auditLogDetailsDialog').show()">
<f:setPropertyActionListener target="#{auditConsultationBean.selectedLog}" value="#{log}" />
</p:commandButton>
</fr:commandButton>
</p:column>
</p:dataTable>
</h:form>
@@ -336,8 +334,8 @@
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<span class="text-600 font-medium">Statut</span>
<p:tag value="#{auditConsultationBean.selectedLog.succes ? 'Succès' : 'Échec'}"
severity="#{auditConsultationBean.selectedLog.succes ? 'success' : 'danger'}" />
<fr:tag value="#{auditConsultationBean.selectedLog.succes ? 'Succès' : 'Échec'}"
severity="#{auditConsultationBean.selectedLog.succes ? 'success' : 'danger'}" />
</div>
</div>
@@ -409,12 +407,11 @@
</div>
<div class="flex justify-content-end mt-4">
<p:commandButton
value="Fermer"
icon="pi pi-times"
styleClass="p-button-secondary"
onclick="PF('auditLogDetailsDialog').hide()"
type="button" />
<fr:commandButton value="Fermer"
icon="pi pi-times"
severity="secondary"
onclick="PF('auditLogDetailsDialog').hide()"
type="button" />
</div>
</h:form>
</p:dialog>

View File

@@ -4,6 +4,7 @@
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya"
template="/templates/main-template.xhtml">
<ui:define name="title">Tableau de Bord - Lions User Manager</ui:define>
@@ -21,114 +22,174 @@
<i class="pi pi-home text-blue-500" style="font-size: 2rem"></i>
<div>
<h3 class="m-0 mb-1">Tableau de Bord</h3>
<p class="text-600 m-0">Vue d'ensemble de la gestion des utilisateurs Keycloak</p>
<p class="text-600 m-0">Vue d'ensemble de la gestion des utilisateurs - Realm: #{dashboardBean.realmName}</p>
</div>
</div>
<p:commandButton
value="Rafraîchir"
icon="pi pi-refresh"
styleClass="p-button-secondary"
action="#{dashboardBean.refreshStatistics}"
update=":formDashboard" />
<fr:commandButton value="Rafraîchir"
icon="pi pi-refresh"
severity="secondary"
action="#{dashboardBean.refreshStatistics}"
update=":formDashboard" />
</div>
</div>
</div>
<!-- ================================================================
STATISTIQUES PRINCIPALES (4 KPI CARDS)
STATISTIQUES PRINCIPALES - KPIs MÉTIER
================================================================ -->
<div class="col-12">
<h5 class="mb-3">Statistiques Principales</h5>
<h5 class="mb-3">Indicateurs Clés de Performance</h5>
</div>
<!-- KPI 1: Utilisateurs Actifs -->
<!-- KPI 1: Total Utilisateurs -->
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg hover:surface-100 cursor-pointer transition-colors transition-duration-150">
<p:commandButton
styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
outcome="/pages/user-manager/users/list">
<div class="flex align-items-start justify-content-between mb-3">
<fr:commandButton styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
outcome="/pages/user-manager/users/list">
<div class="flex align-items-start justify-content-between">
<div>
<div class="text-500 font-medium mb-1">Utilisateurs Actifs</div>
<div class="text-900 font-bold text-2xl">#{dashboardBean.totalUsersDisplay}</div>
<div class="text-500 font-medium mb-2 text-sm uppercase">Total Utilisateurs</div>
<div class="text-900 font-bold text-4xl">#{dashboardBean.totalUsersDisplay}</div>
<div class="text-600 text-sm mt-2">
<i class="pi pi-users mr-2"></i>
Dans le système
</div>
</div>
<div class="flex align-items-center justify-content-center bg-blue-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-users text-blue-600 text-xl"></i>
style="width: 3.5rem; height: 3.5rem">
<i class="pi pi-users text-blue-600" style="font-size: 1.75rem"></i>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-arrow-right text-600"></i>
<span class="ml-2">Total utilisateurs</span>
</div>
</p:commandButton>
</fr:commandButton>
</div>
</div>
<!-- KPI 2: Rôles Realm -->
<!-- KPI 2: Utilisateurs Actifs -->
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg hover:surface-100 cursor-pointer transition-colors transition-duration-150">
<p:commandButton
styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
outcome="/pages/user-manager/roles/list">
<div class="flex align-items-start justify-content-between mb-3">
<fr:commandButton styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
outcome="/pages/user-manager/users/list">
<div class="flex align-items-start justify-content-between">
<div>
<div class="text-500 font-medium mb-1">Rôles Realm</div>
<div class="text-900 font-bold text-2xl">#{dashboardBean.totalRolesDisplay}</div>
<div class="text-500 font-medium mb-2 text-sm uppercase">Utilisateurs Actifs</div>
<div class="text-900 font-bold text-4xl">#{dashboardBean.activeUsersDisplay}</div>
<div class="text-600 text-sm mt-2">
<i class="pi pi-check-circle mr-2"></i>
Comptes activés
</div>
</div>
<div class="flex align-items-center justify-content-center bg-green-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-shield text-green-600 text-xl"></i>
style="width: 3.5rem; height: 3.5rem">
<i class="pi pi-check-circle text-green-600" style="font-size: 1.75rem"></i>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-arrow-right text-600"></i>
<span class="ml-2">Rôles configurés</span>
</div>
</p:commandButton>
</fr:commandButton>
</div>
</div>
<!-- KPI 3: Actions Récentes -->
<!-- KPI 3: Utilisateurs Inactifs -->
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg hover:surface-100 cursor-pointer transition-colors transition-duration-150">
<p:commandButton
styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
outcome="/pages/user-manager/audit/logs">
<div class="flex align-items-start justify-content-between mb-3">
<fr:commandButton styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
outcome="/pages/user-manager/users/list">
<div class="flex align-items-start justify-content-between">
<div>
<div class="text-500 font-medium mb-1">Actions Récentes</div>
<div class="text-900 font-bold text-2xl">#{dashboardBean.recentActionsDisplay}</div>
<div class="text-500 font-medium mb-2 text-sm uppercase">Utilisateurs Inactifs</div>
<div class="text-900 font-bold text-4xl">#{dashboardBean.inactiveUsersDisplay}</div>
<div class="text-600 text-sm mt-2">
<i class="pi pi-ban mr-2"></i>
Comptes désactivés
</div>
</div>
<div class="flex align-items-center justify-content-center bg-orange-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-history text-orange-600 text-xl"></i>
style="width: 3.5rem; height: 3.5rem">
<i class="pi pi-ban text-orange-600" style="font-size: 1.75rem"></i>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-arrow-right text-600"></i>
<span class="ml-2">Dernières 24h</span>
</div>
</p:commandButton>
</fr:commandButton>
</div>
</div>
<!-- KPI 4: Taux d'Activation -->
<!-- KPI 4: Taux de Succès 24h -->
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div>
<div class="text-500 font-medium mb-1">Realm Actif</div>
<div class="text-900 font-bold text-xl" style="word-break: break-word;">lions-user-manager</div>
</div>
<div class="flex align-items-center justify-content-center bg-purple-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-globe text-purple-600 text-xl"></i>
<div class="card surface-0 border-round-lg hover:surface-100 cursor-pointer transition-colors transition-duration-150">
<fr:commandButton styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
outcome="/pages/user-manager/audit/logs">
<div class="flex align-items-start justify-content-between">
<div>
<div class="text-500 font-medium mb-2 text-sm uppercase">Taux de Succès</div>
<div class="text-900 font-bold text-4xl">#{dashboardBean.successRate24hDisplay}</div>
<div class="text-600 text-sm mt-2">
<i class="pi pi-chart-line mr-2"></i>
Dernières 24h
</div>
</div>
<div class="flex align-items-center justify-content-center bg-cyan-100 border-circle"
style="width: 3.5rem; height: 3.5rem">
<i class="pi pi-chart-line text-cyan-600" style="font-size: 1.75rem"></i>
</div>
</div>
</fr:commandButton>
</div>
</div>
<!-- ================================================================
ACTIVITÉ & PERFORMANCE
================================================================ -->
<div class="col-12 lg:col-6">
<div class="card h-full">
<div class="flex align-items-center gap-2 mb-4">
<i class="pi pi-history text-purple-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Activité &amp; Performance</h5>
</div>
<div class="flex align-items-center gap-2">
<p:tag value="Opérationnel" severity="success" styleClass="text-xs" />
<span class="text-500 text-sm">Realm Keycloak</span>
<div class="grid">
<!-- Actions 24h -->
<div class="col-12 md:col-4">
<div class="surface-50 border-round p-3 text-center">
<div class="text-500 text-xs uppercase mb-2">Actions 24h</div>
<div class="text-900 font-bold text-3xl mb-2">#{dashboardBean.actionsLast24hDisplay}</div>
<fr:tag value="Dernières 24h" severity="info" styleClass="text-xs" />
</div>
</div>
<!-- Actions 7j -->
<div class="col-12 md:col-4">
<div class="surface-50 border-round p-3 text-center">
<div class="text-500 text-xs uppercase mb-2">Actions 7j</div>
<div class="text-900 font-bold text-3xl mb-2">#{dashboardBean.actionsLast7dDisplay}</div>
<fr:tag value="Derniers 7 jours" severity="info" styleClass="text-xs" />
</div>
</div>
<!-- Taux de réussite -->
<div class="col-12 md:col-4">
<div class="surface-50 border-round p-3 text-center">
<div class="text-500 text-xs uppercase mb-2">Performance</div>
<div class="text-900 font-bold text-3xl mb-2">#{dashboardBean.successRate24hDisplay}</div>
<fr:tag value="Taux de succès" severity="success" styleClass="text-xs" />
</div>
</div>
<!-- Détails succès/échecs -->
<div class="col-12 mt-3">
<div class="surface-100 border-round p-3">
<div class="flex align-items-center justify-content-between mb-2">
<div class="flex align-items-center gap-2">
<i class="pi pi-check-circle text-green-500"></i>
<span class="text-700 font-medium">Actions réussies</span>
</div>
<span class="font-bold text-900">#{dashboardBean.successfulActions24h}</span>
</div>
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-times-circle text-red-500"></i>
<span class="text-700 font-medium">Actions échouées</span>
</div>
<span class="font-bold text-900">#{dashboardBean.failedActions24h}</span>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -145,32 +206,32 @@
<div class="grid">
<div class="col-12 md:col-6">
<p:commandButton
value="Nouvel Utilisateur"
icon="pi pi-user-plus"
styleClass="w-full p-button-success mb-2"
outcome="/pages/user-manager/users/create" />
<fr:commandButton value="Nouvel Utilisateur"
icon="pi pi-user-plus"
severity="success"
styleClass="w-full mb-2"
outcome="/pages/user-manager/users/create" />
</div>
<div class="col-12 md:col-6">
<p:commandButton
value="Liste des Utilisateurs"
icon="pi pi-users"
styleClass="w-full p-button-primary mb-2"
outcome="/pages/user-manager/users/list" />
<fr:commandButton value="Liste des Utilisateurs"
icon="pi pi-users"
severity="primary"
styleClass="w-full mb-2"
outcome="/pages/user-manager/users/list" />
</div>
<div class="col-12 md:col-6">
<p:commandButton
value="Gestion des Rôles"
icon="pi pi-shield"
styleClass="w-full p-button-info mb-2"
outcome="/pages/user-manager/roles/list" />
<fr:commandButton value="Gestion des Rôles"
icon="pi pi-shield"
severity="info"
styleClass="w-full mb-2"
outcome="/pages/user-manager/roles/list" />
</div>
<div class="col-12 md:col-6">
<p:commandButton
value="Journal d'Audit"
icon="pi pi-history"
styleClass="w-full p-button-help mb-2"
outcome="/pages/user-manager/audit/logs" />
<fr:commandButton value="Journal d'Audit"
icon="pi pi-history"
severity="help"
styleClass="w-full mb-2"
outcome="/pages/user-manager/audit/logs" />
</div>
</div>
@@ -179,7 +240,113 @@
<i class="pi pi-lightbulb text-orange-500"></i>
<div>
<div class="text-700 font-semibold text-sm">Conseil</div>
<small class="text-600">Utilisez les raccourcis ci-dessus pour accéder rapidement aux fonctionnalités principales</small>
<small class="text-600">Utilisez ces raccourcis pour accéder rapidement aux fonctionnalités principales</small>
</div>
</div>
</div>
</div>
</div>
<!-- ================================================================
ALERTES DE SÉCURITÉ (Conditionnel)
================================================================ -->
<h:panelGroup layout="block" styleClass="col-12" rendered="#{dashboardBean.hasAlerts()}">
<div class="card border-left-3 border-red-500">
<div class="flex align-items-center gap-2 mb-4">
<i class="pi pi-exclamation-triangle text-red-500" style="font-size: 1.5rem"></i>
<h5 class="m-0 text-red-600">Alertes de Sécurité</h5>
</div>
<div class="grid">
<!-- Actions critiques -->
<div class="col-12 md:col-4">
<div class="surface-50 border-round p-3 text-center">
<i class="pi pi-shield text-red-500 mb-2" style="font-size: 2rem"></i>
<div class="text-900 font-bold text-2xl mb-1">#{dashboardBean.criticalActions24hDisplay}</div>
<div class="text-600 text-sm">Actions critiques</div>
<small class="text-500">Dernières 24h</small>
</div>
</div>
<!-- Tentatives échouées -->
<div class="col-12 md:col-4">
<div class="surface-50 border-round p-3 text-center">
<i class="pi pi-lock text-orange-500 mb-2" style="font-size: 2rem"></i>
<div class="text-900 font-bold text-2xl mb-1">#{dashboardBean.failedLogins24hDisplay}</div>
<div class="text-600 text-sm">Connexions échouées</div>
<small class="text-500">Dernières 24h</small>
</div>
</div>
<!-- Utilisateurs à risque -->
<div class="col-12 md:col-4">
<div class="surface-50 border-round p-3 text-center">
<i class="pi pi-user-minus text-red-500 mb-2" style="font-size: 2rem"></i>
<div class="text-900 font-bold text-2xl mb-1">#{dashboardBean.usersAtRiskDisplay}</div>
<div class="text-600 text-sm">Utilisateurs à risque</div>
<small class="text-500">Nécessitent attention</small>
</div>
</div>
</div>
<div class="mt-3 surface-100 border-round p-3 border-left-3 border-orange-500">
<div class="flex align-items-center gap-2">
<i class="pi pi-info-circle text-orange-500"></i>
<div>
<div class="text-700 font-semibold text-sm">Recommandation</div>
<small class="text-600">Consultez le journal d'audit pour analyser les événements suspects</small>
</div>
</div>
</div>
</div>
</h:panelGroup>
<!-- ================================================================
RESSOURCES MÉTIER
================================================================ -->
<div class="col-12 lg:col-6">
<div class="card h-full">
<div class="flex align-items-center gap-2 mb-4">
<i class="pi pi-database text-blue-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Ressources</h5>
</div>
<div class="grid">
<!-- Total Rôles -->
<div class="col-12">
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-3">
<div class="flex align-items-center justify-content-center bg-green-100 border-circle"
style="width: 3rem; height: 3rem">
<i class="pi pi-shield text-green-600 text-xl"></i>
</div>
<div>
<div class="text-500 text-xs uppercase mb-1">Rôles Realm</div>
<div class="text-900 font-bold text-2xl">#{dashboardBean.totalRolesDisplay}</div>
</div>
</div>
<fr:commandButton icon="pi pi-arrow-right"
text="true"
severity="secondary"
outcome="/pages/user-manager/roles/list" />
</div>
</div>
</div>
<!-- Realm actif -->
<div class="col-12">
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-globe text-500"></i>
<span class="text-600 font-medium">Realm Keycloak</span>
</div>
<div class="flex align-items-center gap-2">
<span class="font-semibold text-900">#{dashboardBean.realmName}</span>
<fr:tag value="Actif" severity="success" styleClass="text-xs" />
</div>
</div>
</div>
</div>
</div>
@@ -192,7 +359,7 @@
<div class="col-12 lg:col-6">
<div class="card h-full">
<div class="flex align-items-center gap-2 mb-4">
<i class="pi pi-info-circle text-blue-500" style="font-size: 1.5rem"></i>
<i class="pi pi-info-circle text-cyan-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Informations Système</h5>
</div>
@@ -202,34 +369,12 @@
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-tag text-500"></i>
<span class="text-600 font-medium">Version</span>
<span class="text-600 font-medium">Version Application</span>
</div>
<span class="font-semibold text-900">1.0.0</span>
</div>
</div>
<!-- Realm Keycloak -->
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-globe text-500"></i>
<span class="text-600 font-medium">Realm Keycloak</span>
</div>
<span class="font-semibold text-900">lions-user-manager</span>
</div>
</div>
<!-- Statut -->
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-check-circle text-500"></i>
<span class="text-600 font-medium">Statut</span>
</div>
<p:tag value="Opérationnel" severity="success" />
</div>
</div>
<!-- Framework -->
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
@@ -237,90 +382,18 @@
<i class="pi pi-code text-500"></i>
<span class="text-600 font-medium">Framework</span>
</div>
<span class="font-semibold text-900 text-right">Quarkus 3.15.1</span>
<span class="font-semibold text-900 text-right">Quarkus + PrimeFaces Freya</span>
</div>
</div>
<!-- Interface -->
<!-- Statut -->
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-palette text-500"></i>
<span class="text-600 font-medium">Interface</span>
<i class="pi pi-check-circle text-500"></i>
<span class="text-600 font-medium">Statut Système</span>
</div>
<span class="font-semibold text-900">PrimeFaces Freya</span>
</div>
</div>
<!-- Environnement -->
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-server text-500"></i>
<span class="text-600 font-medium">Environnement</span>
</div>
<p:tag value="Développement" severity="warning" styleClass="text-xs" />
</div>
</div>
</div>
</div>
</div>
<!-- ================================================================
ACTIVITÉS RÉCENTES
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between mb-4">
<div class="flex align-items-center gap-2">
<i class="pi pi-clock text-purple-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Activités Récentes</h5>
</div>
<p:commandButton
value="Voir tout"
icon="pi pi-arrow-right"
styleClass="p-button-text p-button-sm"
outcome="/pages/user-manager/audit/logs" />
</div>
<div class="grid">
<!-- Statistique 1: Utilisateurs créés aujourd'hui -->
<div class="col-12 md:col-6 lg:col-3">
<div class="surface-50 border-round p-3 text-center">
<i class="pi pi-user-plus text-blue-500 mb-2" style="font-size: 2rem"></i>
<div class="text-900 font-bold text-xl mb-1">0</div>
<div class="text-600 text-sm">Utilisateurs créés</div>
<small class="text-500">Aujourd'hui</small>
</div>
</div>
<!-- Statistique 2: Rôles modifiés -->
<div class="col-12 md:col-6 lg:col-3">
<div class="surface-50 border-round p-3 text-center">
<i class="pi pi-shield text-green-500 mb-2" style="font-size: 2rem"></i>
<div class="text-900 font-bold text-xl mb-1">0</div>
<div class="text-600 text-sm">Rôles modifiés</div>
<small class="text-500">Cette semaine</small>
</div>
</div>
<!-- Statistique 3: Sessions actives -->
<div class="col-12 md:col-6 lg:col-3">
<div class="surface-50 border-round p-3 text-center">
<i class="pi pi-circle-fill text-orange-500 mb-2" style="font-size: 2rem; animation: pulse 2s ease-in-out infinite;"></i>
<div class="text-900 font-bold text-xl mb-1">-</div>
<div class="text-600 text-sm">Sessions actives</div>
<small class="text-500">En temps réel</small>
</div>
</div>
<!-- Statistique 4: Actions critiques -->
<div class="col-12 md:col-6 lg:col-3">
<div class="surface-50 border-round p-3 text-center">
<i class="pi pi-exclamation-triangle text-red-500 mb-2" style="font-size: 2rem"></i>
<div class="text-900 font-bold text-xl mb-1">0</div>
<div class="text-600 text-sm">Actions critiques</div>
<small class="text-500">24 dernières heures</small>
<fr:tag value="Opérationnel" severity="success" />
</div>
</div>
</div>

View File

@@ -1,304 +0,0 @@
<!DOCTYPE html>
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya"
template="/templates/main-template.xhtml">
<f:metadata>
<f:viewParam name="userId" value="#{userProfilBean.userId}" />
<f:viewParam name="realm" value="#{userProfilBean.realmName}" />
</f:metadata>
<ui:param name="page" value="#{userProfilBean}"/>
<ui:define name="title">Attribution de Rôles - Lions User Manager</ui:define>
<ui:define name="content">
<div class="grid">
<!-- ================================================================
EN-TÊTE DE LA PAGE
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2">
<i class="pi pi-key text-purple-500" style="font-size: 2rem"></i>
<div>
<h3 class="m-0 mb-1">Attribution de Rôles</h3>
<p class="text-600 m-0">Gérer les rôles de l'utilisateur</p>
</div>
</div>
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button p-button-text">
<i class="pi pi-arrow-left mr-2"></i>
Retour à la liste
</h:link>
</div>
</div>
</div>
<!-- ================================================================
INFORMATIONS UTILISATEUR
================================================================ -->
<div class="col-12">
<div class="card">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-user text-blue-500"></i>
Informations de l'Utilisateur
</h3>
<h:panelGroup rendered="#{userProfilBean.user != null}">
<div class="grid">
<div class="col-12 md:col-4">
<div class="surface-50 border-round p-3 text-center">
<!-- Avatar -->
<div style="width: 80px; height: 80px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-color), var(--primary-600, #387FE9)); display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem auto; font-size: 2rem; font-weight: bold; color: white; box-shadow: 0 4px 12px rgba(0,0,0,0.12);">
<h:outputText value="#{userProfilBean.user.username.substring(0,2).toUpperCase()}" />
</div>
<h4 class="text-900 font-semibold m-0 mb-1">#{userProfilBean.user.username}</h4>
<p class="text-600 m-0 text-sm">#{userProfilBean.user.email}</p>
</div>
</div>
<div class="col-12 md:col-8">
<div class="grid">
<div class="col-12 md:col-6">
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Prénom</label>
<p class="text-900 m-0">#{userProfilBean.user.prenom}</p>
</div>
</div>
<div class="col-12 md:col-6">
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Nom</label>
<p class="text-900 m-0">#{userProfilBean.user.nom}</p>
</div>
</div>
<div class="col-12 md:col-6">
<div class="mb-3">
<label class="block text-600 font-medium mb-1 text-sm">Email</label>
<p class="text-900 m-0">#{userProfilBean.user.email}</p>
</div>
</div>
<div class="col-12 md:col-6">
<div class="mb-0">
<label class="block text-600 font-medium mb-2 text-sm">Statut</label>
<div class="flex align-items-center">
<span class="inline-flex align-items-center px-2 py-1 border-round text-xs font-semibold"
style="background-color: #{userProfilBean.user.enabled ? '#C8E6C9' : '#FFCDD2'}; color: #{userProfilBean.user.enabled ? '#2E7D32' : '#C62828'};">
<i class="pi #{userProfilBean.user.enabled ? 'pi-check-circle' : 'pi-times-circle'} mr-1"></i>
<h:outputText value="Actif" rendered="#{userProfilBean.user.enabled}" />
<h:outputText value="Inactif" rendered="#{!userProfilBean.user.enabled}" />
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</h:panelGroup>
<h:panelGroup rendered="#{userProfilBean.user == null}">
<div class="text-center p-5">
<i class="pi pi-exclamation-triangle text-orange-500" style="font-size: 4rem"></i>
<h4 class="text-900 mt-4 mb-2">Utilisateur non trouvé</h4>
<p class="text-600 mb-3">
<h:outputText value="Aucun ID d'utilisateur fourni" rendered="#{userProfilBean.userId == null or userProfilBean.userId == ''}" />
<h:outputText value="L'utilisateur avec l'ID '#{userProfilBean.userId}' n'existe pas dans le realm '#{userProfilBean.realmName}'" rendered="#{userProfilBean.userId != null and userProfilBean.userId != ''}" />
</p>
<small class="text-500 block mb-4">Pour assigner des rôles, accédez à cette page depuis la liste des utilisateurs</small>
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button p-button-primary">
<i class="pi pi-list mr-2"></i>
Aller à la liste des utilisateurs
</h:link>
</div>
</h:panelGroup>
</div>
</div>
<!-- ================================================================
GESTION DES RÔLES
================================================================ -->
<h:panelGroup rendered="#{userProfilBean.user != null}">
<div class="col-12 lg:col-6">
<div class="card h-full">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-shield text-green-500"></i>
Rôles Actuels
</h3>
<h:form id="formCurrentRoles">
<!-- Liste des rôles actuels -->
<div class="flex flex-column gap-2">
<ui:repeat value="#{userProfilBean.user.realmRoles}" var="role">
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<div class="flex align-items-center gap-2 flex-grow-1">
<i class="pi pi-tag text-purple-500"></i>
<div>
<div class="text-900 font-semibold">#{role}</div>
<small class="text-500">Rôle Realm</small>
</div>
</div>
<p:commandButton icon="pi pi-times"
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
title="Retirer ce rôle"
action="#{roleGestionBean.revokeRoleFromUser(userProfilBean.userId, role)}"
update=":formCurrentRoles :formAvailableRoles"
oncomplete="PF('formCurrentRoles').refresh();">
<p:confirm header="Confirmation"
message="Voulez-vous vraiment retirer le rôle '#{role}' ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</div>
</div>
</ui:repeat>
<!-- Message si aucun rôle -->
<div class="text-center p-4" rendered="#{userProfilBean.user.realmRoles == null or userProfilBean.user.realmRoles.size() == 0}">
<i class="pi pi-inbox text-400" style="font-size: 2.5rem"></i>
<p class="text-600 mt-3 mb-0">Aucun rôle assigné</p>
<small class="text-500">Assignez des rôles depuis la liste disponible</small>
</div>
</div>
<div class="mt-4 flex align-items-center justify-content-between surface-100 border-round p-3">
<div class="flex align-items-center gap-2">
<i class="pi pi-info-circle text-blue-500"></i>
<span class="text-700 font-semibold">Total: #{userProfilBean.user.realmRoles != null ? userProfilBean.user.realmRoles.size() : 0} rôle(s)</span>
</div>
<fr:commandButton value="Rafraîchir"
icon="pi pi-refresh"
outlined="true"
size="small"
action="#{userProfilBean.loadUser}"
update=":formCurrentRoles :formAvailableRoles" />
</div>
</h:form>
</div>
</div>
<div class="col-12 lg:col-6">
<div class="card h-full">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-plus-circle text-blue-500"></i>
Rôles Disponibles
</h3>
<h:form id="formAvailableRoles">
<p:messages id="messages" showDetail="true" closable="true">
<p:autoUpdate />
</p:messages>
<!-- Liste des rôles disponibles -->
<div class="flex flex-column gap-2">
<ui:repeat value="#{roleGestionBean.realmRoles}" var="role">
<!-- N'afficher que si le rôle n'est pas déjà assigné -->
<h:panelGroup rendered="#{!userProfilBean.user.realmRoles.contains(role.name)}">
<div class="surface-50 border-round p-3">
<div class="flex align-items-center justify-content-between">
<div class="flex-grow-1">
<div class="text-900 font-semibold flex align-items-center gap-2 mb-1">
<i class="pi pi-tag text-blue-500"></i>
<span>#{role.name}</span>
</div>
<p class="text-600 text-sm m-0">
<h:outputText value="#{role.description}" rendered="#{role.description != null and role.description != ''}" />
<h:outputText value="Aucune description" styleClass="text-500 italic" rendered="#{role.description == null or role.description == ''}" />
</p>
</div>
<p:commandButton icon="pi pi-plus"
styleClass="p-button-rounded p-button-success p-button-sm"
title="Assigner ce rôle"
action="#{roleGestionBean.assignRoleToUser(userProfilBean.userId, role.name)}"
update=":formCurrentRoles :formAvailableRoles"
oncomplete="PF('formAvailableRoles').refresh();" />
</div>
</div>
</h:panelGroup>
</ui:repeat>
<!-- Message si aucun rôle disponible -->
<div class="text-center p-4" rendered="#{roleGestionBean.realmRoles == null or roleGestionBean.realmRoles.size() == 0}">
<i class="pi pi-inbox text-400" style="font-size: 2.5rem"></i>
<p class="text-600 mt-3 mb-0">Aucun rôle disponible</p>
<small class="text-500">Créez des rôles depuis la page de gestion des rôles</small>
</div>
</div>
<div class="mt-4 surface-100 border-round p-3">
<div class="flex align-items-center gap-2">
<i class="pi pi-lightbulb text-orange-500"></i>
<div>
<div class="text-700 font-semibold text-sm">Astuce</div>
<small class="text-600">Cliquez sur <i class="pi pi-plus"></i> pour assigner un rôle à l'utilisateur</small>
</div>
</div>
</div>
</h:form>
</div>
</div>
<!-- ================================================================
ACTIONS
================================================================ -->
<div class="col-12">
<div class="card">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-cog text-gray-500"></i>
Actions
</h3>
<h:form id="formActions">
<div class="flex flex-wrap gap-2">
<h:link outcome="/pages/user-manager/users/profile"
styleClass="p-button p-button-outlined">
<f:param name="userId" value="#{userProfilBean.userId}" />
<i class="pi pi-user mr-2"></i>
<span>Voir le Profil</span>
</h:link>
<h:link outcome="/pages/user-manager/users/edit"
styleClass="p-button p-button-outlined">
<f:param name="userId" value="#{userProfilBean.userId}" />
<i class="pi pi-pencil mr-2"></i>
<span>Modifier l'Utilisateur</span>
</h:link>
<h:link outcome="/pages/user-manager/users/list"
styleClass="p-button p-button-outlined p-button-secondary">
<i class="pi pi-list mr-2"></i>
<span>Liste des Utilisateurs</span>
</h:link>
<h:link outcome="/pages/user-manager/roles/list"
styleClass="p-button p-button-outlined p-button-info">
<i class="pi pi-shield mr-2"></i>
<span>Gérer les Rôles</span>
</h:link>
</div>
</h:form>
</div>
</div>
</h:panelGroup>
</div>
<!-- ================================================================
DIALOG DE CONFIRMATION
================================================================ -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
responsive="true" width="400">
<p:commandButton value="Non" type="button"
styleClass="p-button-text"
icon="pi pi-times" />
<p:commandButton value="Oui" type="button"
styleClass="p-button-danger"
icon="pi pi-check" />
</p:confirmDialog>
</ui:define>
</ui:composition>

View File

@@ -42,164 +42,132 @@
</div>
<!-- ================================================================
FILTRES
FILTRES & KPI INTÉGRÉS (Nombre d'or φ = 1.618 → 62%/38%)
================================================================ -->
<div class="col-12">
<div class="card">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-filter text-blue-500"></i>
Filtres
</h3>
<h:form id="formFilters">
<div class="grid">
<!-- Realm -->
<div class="col-12 md:col-4">
<div class="field mb-0">
<label for="realmFilter" class="block text-900 font-medium mb-2">
Realm
</label>
<p:selectOneMenu id="realmFilter"
value="#{roleGestionBean.realmName}"
styleClass="w-full">
<f:selectItem itemLabel="Sélectionner un realm..." itemValue="" />
<f:selectItems value="#{roleGestionBean.availableRealms}"
var="realm"
itemLabel="#{realm}"
itemValue="#{realm}" />
<p:ajax event="change"
listener="#{roleGestionBean.loadRealmRoles}"
update=":formRealmRoles :formClientRoles :formKpis" />
</p:selectOneMenu>
</div>
</div>
<!-- Client -->
<div class="col-12 md:col-4">
<div class="field mb-0">
<label for="clientFilter" class="block text-900 font-medium mb-2">
Client (optionnel)
</label>
<p:selectOneMenu id="clientFilter"
value="#{roleGestionBean.clientName}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les clients" itemValue="" />
<f:selectItems value="#{roleGestionBean.availableClients}"
var="client"
itemLabel="#{client}"
itemValue="#{client}" />
<p:ajax event="change"
listener="#{roleGestionBean.loadClientRoles}"
update=":formClientRoles" />
</p:selectOneMenu>
</div>
</div>
<!-- Type -->
<div class="col-12 md:col-4">
<div class="field mb-0">
<label for="typeFilter" class="block text-900 font-medium mb-2">
Type de rôle
</label>
<p:selectOneMenu id="typeFilter"
value="#{roleGestionBean.selectedTypeRole}"
styleClass="w-full">
<f:selectItem itemLabel="Tous les types" itemValue="" />
<f:selectItems value="#{roleGestionBean.typeRoleOptions}"
var="type"
itemLabel="#{type}"
itemValue="#{type}" />
</p:selectOneMenu>
</div>
</div>
</div>
</h:form>
</div>
</div>
<!-- ================================================================
KPI CARDS
================================================================ -->
<div class="col-12">
<h:form id="formKpis">
<div class="grid">
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div>
<div class="text-500 font-medium mb-1">Rôles Realm</div>
<div class="text-900 font-bold text-2xl">#{roleGestionBean.realmRoles.size()}</div>
<!-- =========== FILTRES (62%) =========== -->
<div class="col-12 lg:col-7">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-filter text-blue-500"></i>
Filtres
</h3>
<h:form id="formFilters">
<div class="grid">
<!-- Realm -->
<div class="col-12">
<fr:fieldSelect id="realmFilter"
label="Realm"
value="#{roleGestionBean.realmName}"
iconLeft="pi pi-globe">
<f:selectItem itemLabel="Sélectionner un realm..." itemValue="" />
<f:selectItems value="#{roleGestionBean.availableRealms}"
var="realm"
itemLabel="#{realm}"
itemValue="#{realm}" />
<p:ajax event="change"
listener="#{roleGestionBean.loadRealmRoles}"
update=":formRealmRoles :formClientRoles :formKpis" />
</fr:fieldSelect>
</div>
<div class="flex align-items-center justify-content-center bg-purple-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-shield text-purple-600 text-xl"></i>
<!-- Client -->
<div class="col-12">
<fr:fieldSelect id="clientFilter"
label="Client (optionnel)"
value="#{roleGestionBean.clientName}"
iconLeft="pi pi-box">
<f:selectItem itemLabel="Tous les clients" itemValue="" />
<f:selectItems value="#{roleGestionBean.availableClients}"
var="client"
itemLabel="#{client}"
itemValue="#{client}" />
<p:ajax event="change"
listener="#{roleGestionBean.loadClientRoles}"
update=":formClientRoles" />
</fr:fieldSelect>
</div>
<!-- Type -->
<div class="col-12">
<fr:fieldSelect id="typeFilter"
label="Type de rôle"
value="#{roleGestionBean.selectedTypeRole}"
iconLeft="pi pi-filter">
<f:selectItem itemLabel="Tous les types" itemValue="" />
<f:selectItems value="#{roleGestionBean.typeRoleOptions}"
var="type"
itemLabel="#{type}"
itemValue="#{type}" />
</fr:fieldSelect>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-globe text-600"></i>
<span class="ml-2">Rôles du realm</span>
</div>
</div>
</h:form>
</div>
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div>
<div class="text-500 font-medium mb-1">Rôles Client</div>
<div class="text-900 font-bold text-2xl">#{roleGestionBean.clientRoles.size()}</div>
</div>
<div class="flex align-items-center justify-content-center bg-blue-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-sitemap text-blue-600 text-xl"></i>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-box text-600"></i>
<span class="ml-2">Rôles spécifiques client</span>
</div>
</div>
</div>
<!-- =========== KPI STATISTIQUES (38%) =========== -->
<div class="col-12 lg:col-5">
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
<i class="pi pi-chart-bar text-purple-500"></i>
Statistiques <small class="text-500 font-normal">(φ = 1.618)</small>
</h3>
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div>
<div class="text-500 font-medium mb-1">Total Rôles</div>
<div class="text-900 font-bold text-2xl">#{roleGestionBean.allRoles.size()}</div>
<h:form id="formKpis">
<div class="grid">
<!-- KPI 1: Rôles Realm -->
<div class="col-12 md:col-6">
<div class="surface-50 border-round p-3 text-center h-full">
<div class="flex align-items-center justify-content-center bg-purple-100 border-circle mx-auto mb-2"
style="width: 2rem; height: 2rem">
<i class="pi pi-shield text-purple-600 text-sm"></i>
</div>
<div class="text-900 font-bold text-xl mb-1">#{roleGestionBean.realmRoles.size()}</div>
<div class="text-500 text-xs">Rôles Realm</div>
</div>
</div>
<div class="flex align-items-center justify-content-center bg-green-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-check-circle text-green-600 text-xl"></i>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-chart-bar text-600"></i>
<span class="ml-2">Tous les rôles configurés</span>
</div>
</div>
</div>
<div class="col-12 md:col-6 lg:col-3">
<div class="card surface-0 border-round-lg">
<div class="flex align-items-start justify-content-between mb-3">
<div>
<div class="text-500 font-medium mb-1">Realm Actif</div>
<div class="text-900 font-bold text-xl">#{roleGestionBean.realmName}</div>
<!-- KPI 2: Rôles Client -->
<div class="col-12 md:col-6">
<div class="surface-50 border-round p-3 text-center h-full">
<div class="flex align-items-center justify-content-center bg-blue-100 border-circle mx-auto mb-2"
style="width: 2rem; height: 2rem">
<i class="pi pi-sitemap text-blue-600 text-sm"></i>
</div>
<div class="text-900 font-bold text-xl mb-1">#{roleGestionBean.clientRoles.size()}</div>
<div class="text-500 text-xs">Rôles Client</div>
</div>
</div>
<div class="flex align-items-center justify-content-center bg-orange-100 border-circle"
style="width: 2.5rem; height: 2.5rem">
<i class="pi pi-database text-orange-600 text-xl"></i>
<!-- KPI 3: Total Rôles -->
<div class="col-12 md:col-6">
<div class="surface-50 border-round p-3 text-center h-full">
<div class="flex align-items-center justify-content-center bg-green-100 border-circle mx-auto mb-2"
style="width: 2rem; height: 2rem">
<i class="pi pi-check-circle text-green-600 text-sm"></i>
</div>
<div class="text-900 font-bold text-xl mb-1">#{roleGestionBean.allRoles.size()}</div>
<div class="text-500 text-xs">Total Rôles</div>
</div>
</div>
<!-- KPI 4: Realm Actif -->
<div class="col-12 md:col-6">
<div class="surface-50 border-round p-3 text-center h-full">
<div class="flex align-items-center justify-content-center bg-orange-100 border-circle mx-auto mb-2"
style="width: 2rem; height: 2rem">
<i class="pi pi-database text-orange-600 text-sm"></i>
</div>
<div class="text-900 font-bold text-sm mb-1" style="word-break: break-all;">#{roleGestionBean.realmName}</div>
<div class="text-500 text-xs">Realm Actif</div>
</div>
</div>
</div>
<div class="text-500 text-sm">
<i class="pi pi-server text-600"></i>
<span class="ml-2">Realm actuellement sélectionné</span>
</div>
</div>
</h:form>
</div>
</div>
</h:form>
</div>
</div>
<!-- ================================================================
@@ -237,15 +205,18 @@
</p>
</div>
<div class="flex align-items-center gap-1">
<p:commandButton icon="pi pi-trash"
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
title="Supprimer"
action="#{roleGestionBean.deleteRealmRole(role.name)}"
update=":formRealmRoles :formKpis">
<fr:commandButton icon="pi pi-trash"
severity="danger"
rounded="true"
text="true"
size="small"
title="Supprimer"
action="#{roleGestionBean.deleteRealmRole(role.name)}"
update=":formRealmRoles :formKpis">
<p:confirm header="Confirmation"
message="Voulez-vous vraiment supprimer le rôle '#{role.name}' ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</fr:commandButton>
</div>
</div>
@@ -317,15 +288,18 @@
</p>
</div>
<div class="flex align-items-center gap-1">
<p:commandButton icon="pi pi-trash"
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
title="Supprimer"
action="#{roleGestionBean.deleteClientRole(role.name)}"
update=":formClientRoles :formKpis">
<fr:commandButton icon="pi pi-trash"
severity="danger"
rounded="true"
text="true"
size="small"
title="Supprimer"
action="#{roleGestionBean.deleteClientRole(role.name)}"
update=":formClientRoles :formKpis">
<p:confirm header="Confirmation"
message="Voulez-vous vraiment supprimer le rôle '#{role.name}' ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</fr:commandButton>
</div>
</div>
@@ -368,150 +342,108 @@
<!-- ================================================================
DIALOG CRÉATION RÔLE REALM
================================================================ -->
<p:dialog header="Nouveau Rôle Realm"
widgetVar="createRealmRoleDialog"
modal="true"
responsive="true"
width="600"
showEffect="fade"
hideEffect="fade">
<h:form id="formCreateRealmRole">
<div class="grid">
<div class="col-12">
<div class="field mb-3">
<label for="realmRoleName" class="block text-900 font-medium mb-2">
Nom du rôle <span class="text-red-500">*</span>
</label>
<p:inputText id="realmRoleName"
value="#{roleGestionBean.newRole.name}"
styleClass="w-full"
required="true"
placeholder="ex: admin_lions">
<f:validateLength minimum="2" maximum="100" />
<f:validateRegex pattern="^[a-zA-Z0-9_-]+$" />
</p:inputText>
<small class="text-500">Lettres, chiffres, underscores et tirets uniquement</small>
</div>
<div class="field mb-0">
<label for="realmRoleDesc" class="block text-900 font-medium mb-2">
Description
</label>
<p:inputTextarea id="realmRoleDesc"
value="#{roleGestionBean.newRole.description}"
styleClass="w-full"
rows="3"
placeholder="Description du rôle...">
</p:inputTextarea>
</div>
</div>
<fr:formDialog widgetVar="createRealmRoleDialog"
header="Nouveau Rôle Realm"
formId="formCreateRealmRole"
saveLabel="Créer"
cancelLabel="Annuler"
saveAction="#{roleGestionBean.createRealmRole}"
update=":formRealmRoles :formKpis :formCreateRealmRole"
width="600">
<div class="grid">
<div class="col-12">
<fr:fieldInput id="realmRoleName"
label="Nom du rôle"
value="#{roleGestionBean.newRole.name}"
required="true"
placeholder="ex: admin_lions"
iconLeft="pi pi-tag"
helpText="Lettres, chiffres, underscores et tirets uniquement">
<f:validateLength for="input" minimum="2" maximum="100" />
<f:validateRegex for="input" pattern="^[a-zA-Z0-9_-]+$" />
</fr:fieldInput>
</div>
<p:messages id="messagesRealmRole" showDetail="true" closable="true" styleClass="mt-3">
<p:autoUpdate />
</p:messages>
</h:form>
<div class="col-12">
<fr:fieldTextarea id="realmRoleDesc"
label="Description"
value="#{roleGestionBean.newRole.description}"
rows="3"
placeholder="Description du rôle..."
iconLeft="pi pi-align-left" />
</div>
</div>
<f:facet name="footer">
<p:commandButton value="Annuler"
icon="pi pi-times"
styleClass="p-button-text"
onclick="PF('createRealmRoleDialog').hide();"
type="button" />
<p:commandButton value="Créer"
icon="pi pi-check"
styleClass="p-button-success"
action="#{roleGestionBean.createRealmRole}"
update=":formRealmRoles :formKpis :formCreateRealmRole"
oncomplete="if (args &amp;&amp; !args.validationFailed) PF('createRealmRoleDialog').hide();" />
</f:facet>
</p:dialog>
<fr:message id="messagesRealmRole" showDetail="true" closable="true">
<p:autoUpdate />
</fr:message>
</fr:formDialog>
<!-- ================================================================
DIALOG CRÉATION RÔLE CLIENT
================================================================ -->
<p:dialog header="Nouveau Rôle Client"
widgetVar="createClientRoleDialog"
modal="true"
responsive="true"
width="600"
showEffect="fade"
hideEffect="fade">
<h:form id="formCreateClientRole">
<div class="grid">
<div class="col-12">
<div class="field mb-3">
<label for="clientRoleName" class="block text-900 font-medium mb-2">
Nom du rôle <span class="text-red-500">*</span>
</label>
<p:inputText id="clientRoleName"
value="#{roleGestionBean.newRole.name}"
styleClass="w-full"
required="true"
placeholder="ex: manager">
<f:validateLength minimum="2" maximum="100" />
<f:validateRegex pattern="^[a-zA-Z0-9_-]+$" />
</p:inputText>
</div>
<div class="field mb-3">
<label for="clientName" class="block text-900 font-medium mb-2">
Client <span class="text-red-500">*</span>
</label>
<p:selectOneMenu id="clientName"
value="#{roleGestionBean.clientName}"
styleClass="w-full"
required="true">
<f:selectItem itemLabel="Sélectionner un client..." itemValue="" />
<f:selectItems value="#{roleGestionBean.availableClients}" />
</p:selectOneMenu>
</div>
<div class="field mb-0">
<label for="clientRoleDesc" class="block text-900 font-medium mb-2">
Description
</label>
<p:inputTextarea id="clientRoleDesc"
value="#{roleGestionBean.newRole.description}"
styleClass="w-full"
rows="3"
placeholder="Description du rôle...">
</p:inputTextarea>
</div>
</div>
<fr:formDialog widgetVar="createClientRoleDialog"
header="Nouveau Rôle Client"
formId="formCreateClientRole"
saveLabel="Créer"
cancelLabel="Annuler"
saveAction="#{roleGestionBean.createClientRole}"
update=":formClientRoles :formKpis :formCreateClientRole"
width="600">
<div class="grid">
<div class="col-12">
<fr:fieldInput id="clientRoleName"
label="Nom du rôle"
value="#{roleGestionBean.newRole.name}"
required="true"
placeholder="ex: manager"
iconLeft="pi pi-tag"
helpText="Lettres, chiffres, underscores et tirets uniquement">
<f:validateLength for="input" minimum="2" maximum="100" />
<f:validateRegex for="input" pattern="^[a-zA-Z0-9_-]+$" />
</fr:fieldInput>
</div>
<p:messages id="messagesClientRole" showDetail="true" closable="true" styleClass="mt-3">
<p:autoUpdate />
</p:messages>
</h:form>
<div class="col-12">
<fr:fieldSelect id="clientName"
label="Client"
value="#{roleGestionBean.clientName}"
required="true"
iconLeft="pi pi-box">
<f:selectItem itemLabel="Sélectionner un client..." itemValue="" />
<f:selectItems value="#{roleGestionBean.availableClients}" />
</fr:fieldSelect>
</div>
<f:facet name="footer">
<p:commandButton value="Annuler"
icon="pi pi-times"
styleClass="p-button-text"
onclick="PF('createClientRoleDialog').hide();"
type="button" />
<p:commandButton value="Créer"
icon="pi pi-check"
styleClass="p-button-success"
action="#{roleGestionBean.createClientRole}"
update=":formClientRoles :formKpis :formCreateClientRole"
oncomplete="if (args &amp;&amp; !args.validationFailed) PF('createClientRoleDialog').hide();" />
</f:facet>
</p:dialog>
<div class="col-12">
<fr:fieldTextarea id="clientRoleDesc"
label="Description"
value="#{roleGestionBean.newRole.description}"
rows="3"
placeholder="Description du rôle..."
iconLeft="pi pi-align-left" />
</div>
</div>
<fr:message id="messagesClientRole" showDetail="true" closable="true">
<p:autoUpdate />
</fr:message>
</fr:formDialog>
<!-- ================================================================
DIALOG DE CONFIRMATION
DIALOG DE CONFIRMATION (Freya Extension)
================================================================ -->
<!-- Le confirmDialog est géré par p:confirm dans les boutons de suppression -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
responsive="true" width="400">
<p:commandButton value="Non" type="button"
styleClass="p-button-text"
icon="pi pi-times" />
<p:commandButton value="Oui" type="button"
styleClass="p-button-danger"
icon="pi pi-check" />
<fr:commandButton value="Non"
type="button"
text="true"
icon="pi pi-times" />
<fr:commandButton value="Oui"
type="button"
severity="danger"
icon="pi pi-check" />
</p:confirmDialog>
</ui:define>

View File

@@ -4,6 +4,7 @@
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
template="/templates/main-template.xhtml">
@@ -24,31 +25,43 @@
<div class="card">
<h5>Informations du compte</h5>
<h:form id="formAccountInfo">
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-4, col-12 md:col-8">
<p:outputLabel for="username" value="Nom d'utilisateur" />
<p:inputText id="username"
value="#{userSessionBean.username}"
readonly="true"
styleClass="w-full" />
<div class="grid">
<div class="col-12 md:col-6">
<fr:fieldInput id="username"
label="Nom d'utilisateur"
value="#{userSessionBean.username}"
disabled="true"
iconLeft="pi pi-user"
helpText="Votre identifiant unique" />
</div>
<p:outputLabel for="email" value="Email" />
<p:inputText id="email"
value="#{userSessionBean.email}"
readonly="true"
styleClass="w-full" />
<div class="col-12 md:col-6">
<fr:fieldInput id="email"
label="Email"
value="#{userSessionBean.email}"
disabled="true"
iconLeft="pi pi-envelope"
helpText="Votre adresse email" />
</div>
<p:outputLabel for="fullName" value="Nom complet" />
<p:inputText id="fullName"
value="#{userSessionBean.fullName}"
readonly="true"
styleClass="w-full" />
<div class="col-12 md:col-6">
<fr:fieldInput id="fullName"
label="Nom complet"
value="#{userSessionBean.fullName}"
disabled="true"
iconLeft="pi pi-id-card"
helpText="Votre nom complet" />
</div>
<p:outputLabel for="mainRole" value="Rôle principal" />
<p:inputText id="mainRole"
value="#{userSessionBean.mainRole}"
readonly="true"
styleClass="w-full" />
</p:panelGrid>
<div class="col-12 md:col-6">
<fr:fieldInput id="mainRole"
label="Rôle principal"
value="#{userSessionBean.mainRole}"
disabled="true"
iconLeft="pi pi-shield"
helpText="Votre rôle dans l'application" />
</div>
</div>
</h:form>
</div>
</div>
@@ -59,35 +72,34 @@
<h5>Préférences</h5>
<h:form id="formPreferences">
<div class="flex flex-column gap-3">
<div class="flex align-items-center justify-content-between">
<span class="text-600">Thème des composants</span>
<p:selectOneMenu value="#{guestPreferences.componentTheme}"
styleClass="w-12rem">
<f:selectItems value="#{guestPreferences.componentThemes}"
var="theme"
itemLabel="#{theme.name}"
itemValue="#{theme.file}" />
<p:ajax event="change" update="@form" />
</p:selectOneMenu>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Mode sombre</span>
<p:selectOneMenu value="#{guestPreferences.darkMode}"
styleClass="w-12rem">
<f:selectItem itemLabel="Clair" itemValue="light" />
<f:selectItem itemLabel="Sombre" itemValue="dark" />
<p:ajax event="change" update="@form" />
</p:selectOneMenu>
</div>
<div class="flex align-items-center justify-content-between">
<span class="text-600">Style d'input</span>
<p:selectOneMenu value="#{guestPreferences.inputStyle}"
styleClass="w-12rem">
<f:selectItem itemLabel="Outlined" itemValue="outlined" />
<f:selectItem itemLabel="Filled" itemValue="filled" />
<p:ajax event="change" update="@form" />
</p:selectOneMenu>
</div>
<fr:fieldSelect id="componentTheme"
label="Thème des composants"
value="#{guestPreferences.componentTheme}"
iconLeft="pi pi-palette">
<f:selectItems value="#{guestPreferences.componentThemes}"
var="theme"
itemLabel="#{theme.name}"
itemValue="#{theme.file}" />
<p:ajax event="change" update="@form" />
</fr:fieldSelect>
<fr:fieldSelect id="darkMode"
label="Mode sombre"
value="#{guestPreferences.darkMode}"
iconLeft="pi pi-moon">
<f:selectItem itemLabel="Clair" itemValue="light" />
<f:selectItem itemLabel="Sombre" itemValue="dark" />
<p:ajax event="change" update="@form" />
</fr:fieldSelect>
<fr:fieldSelect id="inputStyle"
label="Style d'input"
value="#{guestPreferences.inputStyle}"
iconLeft="pi pi-sliders-h">
<f:selectItem itemLabel="Outlined" itemValue="outlined" />
<f:selectItem itemLabel="Filled" itemValue="filled" />
<p:ajax event="change" update="@form" />
</fr:fieldSelect>
</div>
</h:form>
</div>
@@ -97,29 +109,26 @@
<div class="col-12">
<div class="card">
<h5>Actions</h5>
<div class="flex gap-2">
<div class="flex flex-wrap gap-2">
<h:form>
<p:commandButton
value="Rafraîchir les informations"
icon="pi pi-refresh"
styleClass="p-button-secondary"
action="#{userSessionBean.loadUserInfo}"
update="formAccountInfo" />
<fr:commandButton value="Rafraîchir les informations"
icon="pi pi-refresh"
severity="secondary"
action="#{userSessionBean.loadUserInfo}"
update="formAccountInfo" />
</h:form>
<h:form>
<p:commandButton
value="Changer le mot de passe"
icon="pi pi-key"
styleClass="p-button-info"
outcome="/pages/user-manager/users/profile" />
<fr:commandButton value="Changer le mot de passe"
icon="pi pi-key"
severity="info"
outcome="/pages/user-manager/users/profile" />
</h:form>
<h:form>
<p:commandButton
value="Sauvegarder les préférences"
icon="pi pi-save"
styleClass="p-button-success"
action="#{settingsBean.savePreferences}"
update="@form" />
<fr:commandButton value="Sauvegarder les préférences"
icon="pi pi-save"
severity="success"
action="#{settingsBean.savePreferences}"
update="@form" />
</h:form>
</div>
</div>

View File

@@ -4,6 +4,7 @@
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya"
template="/templates/main-template.xhtml">
<ui:define name="title">Synchronisation Keycloak - Lions User Manager</ui:define>

View File

@@ -26,16 +26,19 @@
</div>
</div>
<div class="flex gap-2">
<p:commandButton
<fr:commandButton
icon="pi pi-question-circle"
styleClass="p-button-rounded p-button-text p-button-help"
rounded="true"
text="true"
severity="help"
title="Aide"
type="button"
onclick="PF('helpDialog').show();" />
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button p-button-text">
<i class="pi pi-arrow-left mr-2"></i>
Retour
</h:link>
<fr:button value="Retour"
icon="pi pi-arrow-left"
severity="secondary"
text="true"
outcome="/pages/user-manager/users/list" />
</div>
</div>
</div>
@@ -125,51 +128,46 @@
<div class="grid">
<!-- Mot de passe -->
<div class="col-12 md:col-6">
<div class="field">
<label for="password" class="block text-900 font-medium mb-2">
<i class="pi pi-lock text-500 mr-1"></i>
Mot de passe <span class="text-red-500">*</span>
</label>
<p:password id="password"
value="#{userCreationBean.password}"
styleClass="w-full"
required="true"
feedback="true"
toggleMask="true"
promptLabel="Entrez un mot de passe"
weakLabel="Faible"
goodLabel="Moyen"
strongLabel="Fort"
placeholder="Minimum 8 caractères">
<f:validateLength minimum="8" maximum="100" />
</p:password>
<small class="text-500">
<i class="pi pi-shield mr-1"></i>
Au moins 8 caractères avec lettres et chiffres
</small>
</div>
<fr:fieldPassword id="password"
label="Mot de passe"
value="#{userCreationBean.password}"
required="true"
feedback="true"
toggleMask="true"
promptLabel="Entrez un mot de passe"
weakLabel="Faible"
goodLabel="Moyen"
strongLabel="Fort"
placeholder="Minimum 8 caractères"
iconLeft="pi pi-lock"
helpText="Au moins 8 caractères avec lettres et chiffres">
<f:validateLength for="input" minimum="8" maximum="100" />
<p:ajax event="keyup"
delay="500"
listener="#{userCreationBean.validatePasswordMatch}"
update="passwordConfirm passwordConfirmMsg"
process="@this passwordConfirm" />
</fr:fieldPassword>
</div>
<!-- Confirmation mot de passe -->
<div class="col-12 md:col-6">
<div class="field">
<label for="passwordConfirm" class="block text-900 font-medium mb-2">
<i class="pi pi-lock text-500 mr-1"></i>
Confirmer le mot de passe <span class="text-red-500">*</span>
</label>
<p:password id="passwordConfirm"
value="#{userCreationBean.passwordConfirm}"
styleClass="w-full"
required="true"
feedback="false"
toggleMask="true"
placeholder="Confirmer le mot de passe">
</p:password>
<small class="text-500">
<i class="pi pi-info-circle mr-1"></i>
Doit correspondre au mot de passe
</small>
</div>
<fr:fieldPassword id="passwordConfirm"
label="Confirmer le mot de passe"
value="#{userCreationBean.passwordConfirm}"
required="true"
feedback="false"
toggleMask="true"
placeholder="Confirmer le mot de passe"
iconLeft="pi pi-lock"
helpText="Doit correspondre au mot de passe">
<p:ajax event="keyup"
delay="500"
listener="#{userCreationBean.validatePasswordMatch}"
update="passwordConfirmMsg"
process="@this password" />
</fr:fieldPassword>
<p:message id="passwordConfirmMsg" for="passwordConfirm" display="text" styleClass="mt-2" />
</div>
</div>
@@ -220,26 +218,20 @@
<div class="surface-50 border-round p-3">
<div class="flex flex-column gap-3">
<!-- Compte activé -->
<div class="flex align-items-center">
<p:selectBooleanCheckbox id="enabled"
value="#{userCreationBean.newUser.enabled}">
</p:selectBooleanCheckbox>
<label for="enabled" class="ml-2 mb-0 cursor-pointer">
<span class="font-semibold text-900">Compte activé</span>
<small class="block text-500">L'utilisateur peut se connecter immédiatement</small>
</label>
</div>
<fr:fieldCheckbox id="enabled"
label=""
value="#{userCreationBean.newUser.enabled}"
checkboxLabel="Compte activé">
<small class="block text-500 mt-1">L'utilisateur peut se connecter immédiatement</small>
</fr:fieldCheckbox>
<!-- Email vérifié -->
<div class="flex align-items-center">
<p:selectBooleanCheckbox id="emailVerified"
value="#{userCreationBean.newUser.emailVerified}">
</p:selectBooleanCheckbox>
<label for="emailVerified" class="ml-2 mb-0 cursor-pointer">
<span class="font-semibold text-900">Email vérifié</span>
<small class="block text-500">Marquer l'email comme vérifié</small>
</label>
</div>
<fr:fieldCheckbox id="emailVerified"
label=""
value="#{userCreationBean.newUser.emailVerified}"
checkboxLabel="Email vérifié">
<small class="block text-500 mt-1">Marquer l'email comme vérifié</small>
</fr:fieldCheckbox>
</div>
</div>
</div>
@@ -332,16 +324,17 @@
validateClient="true" />
<!-- Bouton Réinitialiser -->
<p:commandButton value="Réinitialiser"
<fr:commandButton value="Réinitialiser"
icon="pi pi-refresh"
styleClass="p-button-secondary p-button-outlined"
severity="secondary"
outlined="true"
action="#{userCreationBean.resetForm}"
update=":formUserCreation"
immediate="true">
<p:confirm header="Confirmation"
message="Voulez-vous vraiment réinitialiser le formulaire ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</fr:commandButton>
<!-- Bouton Annuler -->
<fr:commandButton value="Annuler"

View File

@@ -91,6 +91,7 @@
placeholder="ex: jean.dupont@example.com"
helpText="Adresse email valide">
<f:validateRegex for="input" pattern="^[A-Za-z0-9+_.-]+@(.+)$" />
<p:ajax event="keyup" delay="500" update="previewPanel" />
</fr:fieldInput>
</div>
@@ -103,6 +104,7 @@
placeholder="ex: Jean"
helpText="Prénom de l'utilisateur">
<f:validateLength for="input" minimum="2" maximum="100" />
<p:ajax event="keyup" delay="500" update="previewPanel" />
</fr:fieldInput>
</div>
@@ -115,6 +117,7 @@
placeholder="ex: Dupont"
helpText="Nom de famille de l'utilisateur">
<f:validateLength for="input" minimum="2" maximum="100" />
<p:ajax event="keyup" delay="500" update="previewPanel" />
</fr:fieldInput>
</div>
@@ -157,26 +160,16 @@
<div class="surface-50 border-round p-3">
<div class="flex flex-column gap-3">
<!-- Compte activé -->
<div class="flex align-items-center">
<p:selectBooleanCheckbox id="enabled"
value="#{userProfilBean.user.enabled}">
</p:selectBooleanCheckbox>
<label for="enabled" class="ml-2 mb-0 cursor-pointer">
<span class="font-semibold text-900">Compte activé</span>
<small class="block text-500">L'utilisateur peut se connecter</small>
</label>
</div>
<fr:fieldCheckbox id="enabled"
label="Compte activé"
value="#{userProfilBean.user.enabled}"
helpText="L'utilisateur peut se connecter" />
<!-- Email vérifié -->
<div class="flex align-items-center">
<p:selectBooleanCheckbox id="emailVerified"
value="#{userProfilBean.user.emailVerified}">
</p:selectBooleanCheckbox>
<label for="emailVerified" class="ml-2 mb-0 cursor-pointer">
<span class="font-semibold text-900">Email vérifié</span>
<small class="block text-500">Marquer l'email comme vérifié</small>
</label>
</div>
<fr:fieldCheckbox id="emailVerified"
label="Emailrifié"
value="#{userProfilBean.user.emailVerified}"
helpText="Marquer l'email comme vérifié" />
</div>
</div>
</div>
@@ -193,9 +186,11 @@
<div class="card sticky" style="top: 1rem;">
<div class="flex align-items-center gap-2 mb-4">
<i class="pi pi-eye text-blue-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Aperçu</h5>
<h5 class="m-0">Aperçu <small class="text-500 font-normal">(Temps réel)</small></h5>
</div>
<h:panelGroup id="previewPanel">
<!-- Avatar Preview -->
<div class="text-center mb-4">
<div style="width: 80px; height: 80px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-color), var(--primary-600)); display: flex; align-items-center; justify-content: center; margin: 0 auto; font-size: 2rem; font-weight: bold; color: white; box-shadow: 0 4px 12px rgba(0,0,0,0.12);">
@@ -250,6 +245,8 @@
</div>
</div>
</div>
</h:panelGroup>
</div>
</div>
@@ -309,17 +306,9 @@
</div>
<!-- ================================================================
DIALOG DE CONFIRMATION
DIALOG DE CONFIRMATION (Freya Extension)
================================================================ -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
responsive="true" width="400">
<p:commandButton value="Non" type="button"
styleClass="p-button-text"
icon="pi pi-times" />
<p:commandButton value="Oui" type="button"
styleClass="p-button-primary"
icon="pi pi-check" />
</p:confirmDialog>
<!-- Suppression désactivée - utiliser la page liste pour supprimer des utilisateurs -->
</ui:define>
</ui:composition>

View File

@@ -27,10 +27,25 @@
</div>
</div>
<div class="flex gap-2">
<fr:commandButton
value="Importer"
icon="pi pi-upload"
severity="info"
outlined="true"
onclick="PF('importUsersDialog').show()"
type="button" />
<fr:commandButton
value="Exporter"
icon="pi pi-download"
severity="secondary"
outlined="true"
action="#{userListBean.exportToCSV}"
ajax="false" />
<fr:commandButton
value="Rafraîchir"
icon="pi pi-refresh"
severity="secondary"
outlined="true"
action="#{userListBean.refreshData}"
update=":formUserList"
process="@this" />
@@ -149,76 +164,67 @@
SECTION RECHERCHE ET FILTRES
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center gap-2 mb-3">
<i class="pi pi-search text-blue-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Recherche et Filtres</h5>
</div>
<fr:panel header="Recherche et Filtres"
toggleable="true"
collapsed="false">
<f:facet name="icons">
<h:panelGroup rendered="#{userListBean.searchText != null or userListBean.selectedStatut != null}">
<fr:tag value="Filtres actifs"
severity="info"
icon="pi pi-filter"
styleClass="mr-2" />
</h:panelGroup>
</f:facet>
<div class="grid">
<div class="col-12 md:col-6 lg:col-4">
<div class="field">
<label for="searchText" class="block text-900 font-medium mb-2">
<i class="pi pi-search text-500 mr-1"></i>
Recherche
</label>
<p:inputText id="searchText"
value="#{userListBean.searchText}"
styleClass="w-full"
placeholder="Nom, email...">
<p:ajax event="keyup"
delay="500"
update=":formUserList:userTable"
listener="#{userListBean.search}" />
</p:inputText>
</div>
<fr:fieldInput id="searchText"
label="Recherche"
value="#{userListBean.searchText}"
placeholder="Nom, email...">
<p:ajax event="keyup"
delay="500"
update=":formUserList:userTable"
listener="#{userListBean.search}" />
</fr:fieldInput>
</div>
<div class="col-12 md:col-6 lg:col-3">
<div class="field">
<label for="realmFilter" class="block text-900 font-medium mb-2">
<i class="pi pi-globe text-500 mr-1"></i>
Realm
</label>
<p:selectOneMenu id="realmFilter"
value="#{userListBean.realmName}"
styleClass="w-full">
<f:selectItems value="#{userListBean.availableRealms}" />
<p:ajax event="change"
update=":formUserList:userTable"
listener="#{userListBean.search}" />
</p:selectOneMenu>
</div>
<fr:fieldSelect id="realmFilter"
label="Realm"
value="#{userListBean.realmName}">
<f:selectItems value="#{userListBean.availableRealms}" />
<p:ajax event="change"
update=":formUserList:userTable"
listener="#{userListBean.search}" />
</fr:fieldSelect>
</div>
<div class="col-12 md:col-6 lg:col-3">
<div class="field">
<label for="statutFilter" class="block text-900 font-medium mb-2">
<i class="pi pi-filter text-500 mr-1"></i>
Statut
</label>
<p:selectOneMenu id="statutFilter"
value="#{userListBean.selectedStatut}"
styleClass="w-full">
<f:selectItem itemLabel="Tous" itemValue="#{null}" />
<f:selectItems value="#{userListBean.statutOptions}" />
<p:ajax update=":formUserList:userTable"
listener="#{userListBean.search}" />
</p:selectOneMenu>
</div>
<fr:fieldSelect id="statutFilter"
label="Statut"
value="#{userListBean.selectedStatut}">
<f:selectItem itemLabel="Tous" itemValue="#{null}" />
<f:selectItems value="#{userListBean.statutOptions}" />
<p:ajax event="change"
update=":formUserList:userTable"
listener="#{userListBean.search}" />
</fr:fieldSelect>
</div>
<div class="col-12 lg:col-2 flex align-items-end">
<div class="col-12 md:col-6 lg:col-2 flex align-items-end">
<fr:commandButton
value="Réinitialiser"
icon="pi pi-refresh"
severity="secondary"
outlined="true"
styleClass="w-full"
action="#{userListBean.resetSearch}"
update=":formUserList:userTable @form" />
update=":formUserList:userTable @form"
rendered="#{userListBean.searchText != null or userListBean.selectedStatut != null}" />
</div>
</div>
</div>
</fr:panel>
</div>
<!-- ================================================================
@@ -244,16 +250,16 @@
var="user"
rowKey="#{user.id}"
paginator="true"
rows="#{userListBean.pageSize != null ? userListBean.pageSize : 10}"
rowsPerPageTemplate="10,20,50"
rows="#{userListBean.pageSize != null ? userListBean.pageSize : 25}"
rowsPerPageTemplate="10,25,50,100"
emptyMessage="Aucun utilisateur trouvé"
reflow="true"
responsiveLayout="scroll"
styleClass="p-datatable-striped">
<p:ajax event="page" listener="#{userListBean.onPageChange}" update=":formUserList:userTable :formUserList:formMessages" />
<!-- Colonne Avatar + Username -->
<p:column headerText="Utilisateur" sortBy="#{user.username}" style="width: 250px">
<p:column headerText="Utilisateur" sortBy="#{user.username}" style="width: 250px" priority="1">
<div class="flex align-items-center gap-3">
<div class="border-circle bg-primary text-white flex align-items-center justify-content-center"
style="width: 42px; height: 42px; flex-shrink: 0;">
@@ -269,7 +275,7 @@
</p:column>
<!-- Colonne Email -->
<p:column headerText="Email" sortBy="#{user.email}" style="width: 250px">
<p:column headerText="Email" sortBy="#{user.email}" style="width: 250px" priority="2">
<div class="flex align-items-center gap-2">
<i class="pi pi-envelope text-500"></i>
<span class="text-900">#{user.email}</span>
@@ -280,13 +286,13 @@
</p:column>
<!-- Colonne Statut -->
<p:column headerText="Statut" sortBy="#{user.enabled}" style="width: 120px; text-align: center">
<p:column headerText="Statut" sortBy="#{user.enabled}" style="width: 120px; text-align: center" priority="3">
<fr:tag value="#{user.enabled ? 'ACTIF' : 'INACTIF'}"
severity="#{user.enabled ? 'success' : 'danger'}" />
</p:column>
<!-- Colonne Rôles -->
<p:column headerText="Rôles" style="width: 250px">
<p:column headerText="Rôles" style="width: 250px" priority="5">
<div class="flex flex-wrap gap-1">
<h:outputText value="Aucun rôle" styleClass="text-500 text-sm"
rendered="#{user.realmRoles == null or user.realmRoles.size() == 0}" />
@@ -304,165 +310,256 @@
</p:column>
<!-- Colonne Actions -->
<p:column headerText="Actions" style="width: 250px; text-align: center">
<div class="flex gap-1 justify-content-center flex-wrap">
<!-- Bouton Voir Profil -->
<p:column headerText="Actions" style="width: 120px; text-align: center" priority="4">
<div class="flex gap-1 justify-content-center">
<!-- Bouton Voir (Action principale) -->
<p:button icon="pi pi-eye"
styleClass="p-button-rounded p-button-text p-button-sm p-button-info"
styleClass="p-button-rounded p-button-sm p-button-info"
title="Voir le profil"
outcome="/pages/user-manager/users/view">
<f:param name="userId" value="#{user.id}" />
<f:param name="realm" value="#{userListBean.realmName}" />
</p:button>
<!-- Bouton Modifier -->
<p:button icon="pi pi-pencil"
styleClass="p-button-rounded p-button-text p-button-sm"
title="Modifier"
outcome="/pages/user-manager/users/edit">
<f:param name="userId" value="#{user.id}" />
<f:param name="realm" value="#{userListBean.realmName}" />
</p:button>
<!-- Menu Actions Secondaires -->
<p:splitButton icon="pi pi-ellipsis-v"
styleClass="p-button-rounded p-button-sm p-button-secondary"
menuStyleClass="text-left">
<p:menuitem value="Modifier"
icon="pi pi-pencil"
outcome="/pages/user-manager/users/edit">
<f:param name="userId" value="#{user.id}" />
<f:param name="realm" value="#{userListBean.realmName}" />
</p:menuitem>
<!-- Bouton Gérer les Rôles -->
<p:button icon="pi pi-key"
styleClass="p-button-rounded p-button-text p-button-sm p-button-help"
title="Gérer les rôles"
outcome="/pages/user-manager/roles/assign">
<f:param name="userId" value="#{user.id}" />
<f:param name="realm" value="#{userListBean.realmName}" />
</p:button>
<p:menuitem value="Gérer les Rôles"
icon="pi pi-key"
outcome="/pages/user-manager/users/edit">
<f:param name="userId" value="#{user.id}" />
<f:param name="realm" value="#{userListBean.realmName}" />
</p:menuitem>
<!-- Bouton Désactiver (si actif) -->
<p:commandButton icon="pi pi-ban"
styleClass="p-button-rounded p-button-text p-button-sm p-button-warning"
title="Désactiver"
action="#{userListBean.deactivateUserAction}"
update=":formUserList:userTable :formUserList:formMessages"
process="@this"
rendered="#{user.enabled}">
<f:attribute name="userId" value="#{user.id}" />
<p:confirm header="Désactiver l'utilisateur"
message="Voulez-vous vraiment désactiver l'utilisateur #{user.username} ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
<p:divider />
<!-- Bouton Activer (si inactif) -->
<fr:commandButton icon="pi pi-check"
rounded="true"
text="true"
size="small"
severity="success"
title="Activer"
action="#{userListBean.activateUserAction}"
update=":formUserList:userTable :formUserList:formMessages"
process="@this"
rendered="#{not user.enabled}">
<f:attribute name="userId" value="#{user.id}" />
</fr:commandButton>
<!-- Désactiver (si utilisateur actif) -->
<p:menuitem value="Désactiver"
icon="pi pi-ban"
styleClass="text-orange-500"
action="#{userListBean.deactivateUserAction}"
update=":formUserList:userTable :formUserList:formMessages"
process="@this"
rendered="#{user.enabled}">
<f:attribute name="userId" value="#{user.id}" />
<p:confirm header="Désactiver l'utilisateur"
message="Voulez-vous vraiment désactiver l'utilisateur #{user.username} ?"
icon="pi pi-exclamation-triangle" />
</p:menuitem>
<!-- Bouton Supprimer -->
<p:commandButton icon="pi pi-trash"
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
title="Supprimer"
action="#{userListBean.deleteUserAction}"
update=":formUserList:userTable :formUserList:formMessages"
process="@this">
<f:attribute name="userId" value="#{user.id}" />
<p:confirm header="Supprimer l'utilisateur"
message="Voulez-vous vraiment supprimer définitivement l'utilisateur #{user.username} ? Cette action est irréversible."
icon="pi pi-exclamation-triangle" />
</p:commandButton>
<!-- Activer (si utilisateur inactif) -->
<p:menuitem value="Activer"
icon="pi pi-check"
styleClass="text-green-500"
action="#{userListBean.activateUserAction}"
update=":formUserList:userTable :formUserList:formMessages"
process="@this"
rendered="#{!user.enabled}">
<f:attribute name="userId" value="#{user.id}" />
<p:confirm header="Activer l'utilisateur"
message="Voulez-vous vraiment activer l'utilisateur #{user.username} ?"
icon="pi pi-exclamation-triangle" />
</p:menuitem>
<p:menuitem value="Supprimer"
icon="pi pi-trash"
styleClass="text-red-500"
action="#{userListBean.deleteUserAction}"
update=":formUserList:userTable :formUserList:formMessages"
process="@this">
<f:attribute name="userId" value="#{user.id}" />
<p:confirm header="Supprimer l'utilisateur"
message="Voulez-vous vraiment supprimer définitivement l'utilisateur #{user.username} ? Cette action est IRRÉVERSIBLE."
icon="pi pi-exclamation-triangle" />
</p:menuitem>
</p:splitButton>
</div>
</p:column>
</p:dataTable>
</div>
</div>
<!-- ================================================================
ACTIONS RAPIDES
================================================================ -->
<div class="col-12">
<div class="card">
<div class="flex align-items-center gap-2 mb-3">
<i class="pi pi-bolt text-orange-500" style="font-size: 1.5rem"></i>
<h5 class="m-0">Actions Rapides</h5>
</div>
<div class="grid">
<div class="col-12 md:col-6 lg:col-3">
<fr:commandButton
value="Créer un Utilisateur"
icon="pi pi-user-plus"
severity="success"
styleClass="w-full"
outcome="/pages/user-manager/users/create" />
</div>
<div class="col-12 md:col-6 lg:col-3">
<fr:commandButton
value="Exporter la Liste"
icon="pi pi-download"
severity="secondary"
styleClass="w-full"
action="#{userListBean.exportToCSV}"
ajax="false" />
</div>
<div class="col-12 md:col-6 lg:col-3">
<fr:commandButton
value="Importer des Utilisateurs"
icon="pi pi-upload"
severity="info"
styleClass="w-full"
onclick="PF('importUsersDialog').show()"
type="button" />
</div>
<div class="col-12 md:col-6 lg:col-3">
<fr:commandButton
value="Gestion des Rôles"
icon="pi pi-shield"
severity="primary"
styleClass="w-full"
outcome="/pages/user-manager/roles/list" />
</div>
</div>
</div>
</div>
</div>
</h:form>
<!-- ================================================================
DIALOG D'IMPORT
DIALOG D'IMPORT CSV (Freya formDialog)
================================================================ -->
<p:dialog id="importUsersDialog"
widgetVar="importUsersDialog"
header="Importer des Utilisateurs"
<p:dialog widgetVar="importUsersDialog"
header="Importer des Utilisateurs depuis CSV"
modal="true"
resizable="false"
styleClass="w-full md:w-30rem">
<h:form id="formImportUsers">
<div class="flex flex-column gap-3">
<p class="text-600">
Importez des utilisateurs depuis un fichier CSV ou JSON.
</p>
<p:fileUpload mode="simple"
skinSimple="true"
accept=".csv,.json"
label="Sélectionner un fichier" />
<div class="flex justify-content-end gap-2 mt-3">
<fr:commandButton value="Annuler"
icon="pi pi-times"
severity="secondary"
onclick="PF('importUsersDialog').hide()"
type="button" />
<fr:commandButton value="Importer"
icon="pi pi-upload"
severity="success"
action="#{userListBean.importUsers}"
update=":formUserList"
oncomplete="PF('importUsersDialog').hide()" />
responsive="true"
width="600"
showEffect="fade"
hideEffect="fade"
closeOnEscape="true">
<h:form id="formImportDialog">
<!-- Instructions -->
<div class="surface-100 border-round p-3 mb-4">
<div class="flex align-items-start gap-2">
<i class="pi pi-info-circle text-blue-500 mt-1"></i>
<div>
<h6 class="mt-0 mb-2">Format du fichier CSV requis:</h6>
<ul class="text-600 text-sm mt-0 mb-0 pl-3">
<li>En-tête: <code class="bg-white px-2 py-1 border-round">username,prenom,nom,email</code></li>
<li>Encodage: UTF-8</li>
<li>Séparateur: virgule (,)</li>
</ul>
</div>
</div>
</div>
<!-- Template CSV téléchargeable -->
<div class="mb-4">
<h6 class="mb-2">Télécharger le template CSV:</h6>
<fr:commandButton value="Télécharger Template CSV"
icon="pi pi-download"
severity="info"
outlined="true"
styleClass="w-full"
action="#{userListBean.downloadCSVTemplate}"
ajax="false" />
<small class="text-500 mt-1">
Utilisez ce template pour préparer votre fichier d'import
</small>
</div>
<fr:divider />
<!-- Upload de fichier -->
<div class="mb-3">
<h6 class="mb-3">Sélectionner le fichier CSV:</h6>
<p:fileUpload id="csvFileUpload"
mode="simple"
skinSimple="true"
label="Choisir un fichier CSV"
chooseIcon="pi pi-folder-open"
accept=".csv"
listener="#{userListBean.handleFileUpload}"
update=":formUserList:userTable :formUserList:formMessages"
oncomplete="PF('importUsersDialog').hide()"
styleClass="w-full" />
</div>
<div class="surface-50 border-round p-3">
<div class="flex align-items-center gap-2">
<i class="pi pi-lightbulb text-orange-500"></i>
<small class="text-600">
<strong>Astuce:</strong> Exportez d'abord vos utilisateurs existants pour voir le format attendu
</small>
</div>
</div>
</h:form>
</p:dialog>
<!-- ================================================================
DIALOG RÉSULTATS D'IMPORT
================================================================ -->
<p:dialog widgetVar="importResultDialog"
header="Résultats de l'Import CSV"
modal="true"
responsive="true"
width="700"
showEffect="fade"
hideEffect="fade"
closeOnEscape="true">
<h:form id="formImportResult">
<h:panelGroup rendered="#{userListBean.lastImportResult != null}">
<!-- Résumé -->
<div class="mb-4">
<h6 class="mb-3">Résumé de l'import:</h6>
<div class="grid">
<div class="col-4">
<div class="surface-100 border-round p-3 text-center">
<div class="text-500 text-xs uppercase mb-1">Total lignes</div>
<div class="text-900 font-bold text-2xl">#{userListBean.lastImportResult.totalLines}</div>
</div>
</div>
<div class="col-4">
<div class="surface-100 border-round p-3 text-center border-left-3 border-green-500">
<div class="text-500 text-xs uppercase mb-1">Succès</div>
<div class="text-green-600 font-bold text-2xl">#{userListBean.lastImportResult.successCount}</div>
</div>
</div>
<div class="col-4">
<div class="surface-100 border-round p-3 text-center border-left-3 border-red-500">
<div class="text-500 text-xs uppercase mb-1">Erreurs</div>
<div class="text-red-600 font-bold text-2xl">#{userListBean.lastImportResult.errorCount}</div>
</div>
</div>
</div>
</div>
<!-- Liste des erreurs détaillées -->
<h:panelGroup rendered="#{userListBean.lastImportResult.errorCount > 0}">
<fr:divider align="left">
<span class="text-red-600 font-bold">Détails des Erreurs</span>
</fr:divider>
<p:dataTable value="#{userListBean.lastImportResult.errors}"
var="error"
paginator="true"
rows="10"
rowsPerPageTemplate="10,20,50"
emptyMessage="Aucune erreur"
styleClass="p-datatable-sm p-datatable-striped">
<p:column headerText="Ligne" style="width: 80px; text-align: center">
<fr:tag value="#{error.lineNumber}"
severity="danger"
styleClass="font-mono" />
</p:column>
<p:column headerText="Type d'erreur" style="width: 150px">
<fr:tag value="#{error.errorType}"
severity="#{error.errorType == 'VALIDATION_ERROR' ? 'warning' : 'danger'}" />
</p:column>
<p:column headerText="Champ" style="width: 120px">
<span class="font-semibold text-900">#{error.field != null ? error.field : '-'}</span>
</p:column>
<p:column headerText="Message d'erreur">
<div class="flex flex-column gap-1">
<span class="text-900">#{error.message}</span>
<h:panelGroup rendered="#{error.lineContent != null and error.lineContent.length() > 0}">
<code class="text-xs bg-red-50 text-red-900 p-2 border-round block overflow-x-auto">#{error.lineContent}</code>
</h:panelGroup>
</div>
</p:column>
</p:dataTable>
</h:panelGroup>
<!-- Message de succès complet -->
<h:panelGroup rendered="#{userListBean.lastImportResult.errorCount == 0}">
<div class="surface-100 border-left-3 border-green-500 border-round p-4 text-center">
<i class="pi pi-check-circle text-green-500" style="font-size: 3rem"></i>
<h5 class="text-green-600 mt-3 mb-2">Import réussi!</h5>
<p class="text-600 m-0">
Tous les utilisateurs ont été importés avec succès.
</p>
</div>
</h:panelGroup>
<!-- Actions -->
<div class="flex justify-content-end gap-2 mt-4">
<fr:commandButton value="Fermer"
icon="pi pi-times"
severity="secondary"
onclick="PF('importResultDialog').hide()"
type="button" />
</div>
</h:panelGroup>
</h:form>
</p:dialog>

View File

@@ -333,26 +333,29 @@
<span>Gestion du Profil</span>
</h4>
<div class="flex flex-column gap-2">
<p:commandButton value="Modifier mon profil"
icon="pi pi-pencil"
styleClass="p-button-outlined w-full justify-content-start"
disabled="true">
<fr:commandButton value="Modifier mon profil"
icon="pi pi-pencil"
outlined="true"
styleClass="w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Fonctionnalité gérée par Keycloak"/>
</p:commandButton>
</fr:commandButton>
<p:commandButton value="Changer mon mot de passe"
icon="pi pi-key"
styleClass="p-button-outlined w-full justify-content-start"
disabled="true">
<fr:commandButton value="Changer mon mot de passe"
icon="pi pi-key"
outlined="true"
styleClass="w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Utilisez le portail Keycloak"/>
</p:commandButton>
</fr:commandButton>
<p:commandButton value="Paramètres de sécurité"
icon="pi pi-shield"
styleClass="p-button-outlined w-full justify-content-start"
disabled="true">
<fr:commandButton value="Paramètres de sécurité"
icon="pi pi-shield"
outlined="true"
styleClass="w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
</p:commandButton>
</fr:commandButton>
</div>
</div>
</div>
@@ -365,28 +368,33 @@
<span>Sessions et Sécurité</span>
</h4>
<div class="flex flex-column gap-2">
<p:commandButton value="Voir mes sessions actives"
icon="pi pi-desktop"
styleClass="p-button-outlined p-button-info w-full justify-content-start"
disabled="true">
<fr:commandButton value="Voir mes sessions actives"
icon="pi pi-desktop"
outlined="true"
severity="info"
styleClass="w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
</p:commandButton>
</fr:commandButton>
<p:commandButton value="Historique des connexions"
icon="pi pi-history"
styleClass="p-button-outlined p-button-secondary w-full justify-content-start"
disabled="true">
<fr:commandButton value="Historique des connexions"
icon="pi pi-history"
outlined="true"
severity="secondary"
styleClass="w-full justify-content-start"
disabled="true">
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
</p:commandButton>
</fr:commandButton>
<p:commandButton value="Se déconnecter"
icon="pi pi-sign-out"
styleClass="p-button-danger w-full justify-content-start"
action="#{userSessionBean.logout}">
<fr:commandButton value="Se déconnecter"
icon="pi pi-sign-out"
severity="danger"
styleClass="w-full justify-content-start"
action="#{userSessionBean.logout}">
<p:confirm header="Confirmation de déconnexion"
message="Êtes-vous sûr de vouloir vous déconnecter ?"
icon="pi pi-exclamation-triangle" />
</p:commandButton>
</fr:commandButton>
</div>
</div>
</div>
@@ -397,16 +405,19 @@
</div>
<!-- ================================================================
DIALOG DE CONFIRMATION
DIALOG DE CONFIRMATION (Freya Extension)
================================================================ -->
<!-- Le confirmDialog est géré par p:confirm dans le bouton de déconnexion -->
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
responsive="true" width="400">
<p:commandButton value="Non" type="button"
styleClass="p-button-text"
icon="pi pi-times" />
<p:commandButton value="Oui" type="button"
styleClass="p-button-danger"
icon="pi pi-check" />
<fr:commandButton value="Non"
type="button"
text="true"
icon="pi pi-times" />
<fr:commandButton value="Oui"
type="button"
severity="danger"
icon="pi pi-check" />
</p:confirmDialog>
<!-- Animation CSS pour le badge "Connecté" -->

View File

@@ -140,11 +140,11 @@
<div class="mb-3 pb-3 border-bottom-1 surface-border">
<label class="block text-600 font-medium mb-2 text-sm">Rôles Realm assignés</label>
<div class="flex flex-wrap gap-2">
<h:outputText value="Aucun rôle"
<h:outputText value="Aucun rôle"
styleClass="text-500 text-sm"
rendered="#{userProfilBean.user.realmRoles == null or userProfilBean.user.realmRoles.size() == 0}" />
<ui:repeat value="#{userProfilBean.user.realmRoles}" var="role">
<p:badge value="#{role}" severity="info" styleClass="text-sm"></p:badge>
<fr:tag value="#{role}" severity="info" styleClass="text-sm" />
</ui:repeat>
</div>
</div>
@@ -152,9 +152,9 @@
<div class="mb-3 pb-3 border-bottom-1 surface-border">
<label class="block text-600 font-medium mb-1 text-sm">Statut du compte</label>
<div class="flex align-items-center">
<p:tag value="#{userProfilBean.user.enabled ? 'ACTIF' : 'INACTIF'}"
severity="#{userProfilBean.user.enabled ? 'success' : 'danger'}"
styleClass="text-sm"></p:tag>
<fr:tag value="#{userProfilBean.user.enabled ? 'ACTIF' : 'INACTIF'}"
severity="#{userProfilBean.user.enabled ? 'success' : 'danger'}"
styleClass="text-sm" />
</div>
</div>
@@ -209,7 +209,7 @@
icon="pi pi-key"
severity="help"
styleClass="w-full"
outcome="/pages/user-manager/roles/assign">
outcome="/pages/user-manager/users/edit">
<f:param name="userId" value="#{userProfilBean.userId}" />
<f:param name="realm" value="#{userProfilBean.realmName}" />
</fr:commandButton>

View File

@@ -37,18 +37,18 @@
<!-- Gestion Rôles -->
<p:submenu id="m_roles" label="Gestion Rôles" icon="pi pi-shield">
<p:menuitem id="m_roles_list" value="Liste des Rôles" icon="pi pi-list" outcome="/pages/user-manager/roles/list" />
<p:menuitem id="m_roles_assign" value="Attribution Rôles" icon="pi pi-key" outcome="/pages/user-manager/roles/assign" />
</p:submenu>
<!-- Audit -->
<p:submenu id="m_audit" label="Audit" icon="pi pi-history">
<p:menuitem id="m_audit_logs" value="Journal d'Audit" icon="pi pi-file-o" outcome="/pages/user-manager/audit/logs" />
</p:submenu>
<!-- Synchronisation -->
<!-- Synchronisation - DÉSACTIVÉ: page stub non implémentée
<p:submenu id="m_sync" label="Synchronisation" icon="pi pi-sync">
<p:menuitem id="m_sync_dashboard" value="Dashboard" icon="pi pi-dashboard" outcome="/pages/user-manager/sync/dashboard" />
</p:submenu>
-->
<!-- Administration (visible uniquement pour les admins) -->
<p:submenu id="m_admin" label="Administration" icon="pi pi-cog" rendered="#{userSessionBean.hasRole('admin')}">

View File

@@ -3,6 +3,7 @@
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:p="http://primefaces.org/ui"
xmlns:fr="http://primefaces.org/freya"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<!--
@@ -44,6 +45,7 @@
</ui:include>
-->
<!-- Valeurs par défaut simplifiées (fr:commandButton gère severity/size nativement) -->
<c:set var="severity" value="#{empty severity ? 'primary' : severity}" />
<c:set var="size" value="#{empty size ? 'normal' : size}" />
<c:set var="disabled" value="#{empty disabled ? false : disabled}" />
@@ -51,47 +53,14 @@
<c:set var="hasAction" value="#{empty hasAction ? false : hasAction}" />
<c:set var="hasOutcome" value="#{empty hasOutcome ? false : hasOutcome}" />
<!-- Déterminer la classe selon la severity -->
<c:choose>
<c:when test="#{severity == 'primary'}">
<c:set var="buttonClass" value="p-button-primary" />
</c:when>
<c:when test="#{severity == 'success'}">
<c:set var="buttonClass" value="p-button-success" />
</c:when>
<c:when test="#{severity == 'warning'}">
<c:set var="buttonClass" value="p-button-warning" />
</c:when>
<c:when test="#{severity == 'danger'}">
<c:set var="buttonClass" value="p-button-danger" />
</c:when>
<c:when test="#{severity == 'info'}">
<c:set var="buttonClass" value="p-button-info" />
</c:when>
<c:otherwise>
<c:set var="buttonClass" value="p-button-secondary" />
</c:otherwise>
</c:choose>
<!-- Ajouter la taille -->
<c:if test="#{size == 'small'}">
<c:set var="buttonClass" value="#{buttonClass} p-button-sm" />
</c:if>
<c:if test="#{size == 'large'}">
<c:set var="buttonClass" value="#{buttonClass} p-button-lg" />
</c:if>
<!-- Ajouter les classes personnalisées -->
<c:if test="#{not empty styleClass}">
<c:set var="buttonClass" value="#{buttonClass} #{styleClass}" />
</c:if>
<c:choose>
<c:when test="#{hasAction}">
<p:commandButton
<fr:commandButton
value="#{value}"
icon="#{not empty icon ? icon : ''}"
styleClass="#{buttonClass}"
severity="#{severity}"
size="#{size != 'normal' ? size : null}"
styleClass="#{styleClass}"
disabled="#{disabled}"
action="#{action}"
update="#{not empty update ? update : '@form'}"
@@ -99,10 +68,12 @@
onclick="#{not empty onclick ? onclick : ''}" />
</c:when>
<c:when test="#{hasOutcome}">
<p:commandButton
<fr:commandButton
value="#{value}"
icon="#{not empty icon ? icon : ''}"
styleClass="#{buttonClass}"
severity="#{severity}"
size="#{size != 'normal' ? size : null}"
styleClass="#{styleClass}"
disabled="#{disabled}"
outcome="#{outcome}"
update="#{not empty update ? update : '@form'}"
@@ -110,19 +81,23 @@
onclick="#{not empty onclick ? onclick : ''}" />
</c:when>
<c:when test="#{not empty onclick}">
<p:commandButton
<fr:commandButton
value="#{value}"
icon="#{not empty icon ? icon : ''}"
styleClass="#{buttonClass}"
severity="#{severity}"
size="#{size != 'normal' ? size : null}"
styleClass="#{styleClass}"
disabled="#{disabled}"
type="button"
onclick="#{onclick}" />
</c:when>
<c:otherwise>
<p:commandButton
<fr:commandButton
value="#{value}"
icon="#{not empty icon ? icon : ''}"
styleClass="#{buttonClass}"
severity="#{severity}"
size="#{size != 'normal' ? size : null}"
styleClass="#{styleClass}"
disabled="true"
title="Aucune action définie" />
</c:otherwise>