From c5cbe61002be28f834e46f5483bd2a66f237c9ba Mon Sep 17 00:00:00 2001 From: lionsdev Date: Sat, 3 Jan 2026 13:53:35 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Optimisations=20UX/UI=20et=20am=C3=A9li?= =?UTF-8?q?oration=20import/export=20CSV?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../client/service/UserServiceClient.java | 20 + .../client/view/AuditConsultationBean.java | 26 +- .../manager/client/view/DashboardBean.java | 384 ++++++++++---- .../manager/client/view/DashboardView.java | 2 + .../manager/client/view/UserCreationBean.java | 26 + .../manager/client/view/UserListBean.java | 140 ++++- src/main/resources/META-INF/faces-config.xml | 17 + .../pages/admin/realm-assignments.xhtml | 252 ++++----- .../pages/user-manager/audit/logs.xhtml | 149 +++--- .../pages/user-manager/dashboard.xhtml | 445 +++++++++------- .../pages/user-manager/roles/assign.xhtml | 304 ----------- .../pages/user-manager/roles/list.xhtml | 496 ++++++++---------- .../pages/user-manager/settings.xhtml | 147 +++--- .../pages/user-manager/sync/dashboard.xhtml | 1 + .../pages/user-manager/users/create.xhtml | 129 +++-- .../pages/user-manager/users/edit.xhtml | 47 +- .../pages/user-manager/users/list.xhtml | 481 ++++++++++------- .../pages/user-manager/users/profile.xhtml | 85 +-- .../pages/user-manager/users/view.xhtml | 12 +- .../templates/components/layout/menu.xhtml | 6 +- .../shared/buttons/button-user-action.xhtml | 61 +-- .../client/view/DashboardBeanTest.java | 3 +- 22 files changed, 1697 insertions(+), 1536 deletions(-) delete mode 100644 src/main/resources/META-INF/resources/pages/user-manager/roles/assign.xhtml diff --git a/src/main/java/dev/lions/user/manager/client/service/UserServiceClient.java b/src/main/java/dev/lions/user/manager/client/service/UserServiceClient.java index 22f3c53..57d3657 100644 --- a/src/main/java/dev/lions/user/manager/client/service/UserServiceClient.java +++ b/src/main/java/dev/lions/user/manager/client/service/UserServiceClient.java @@ -150,5 +150,25 @@ public interface UserServiceClient { @PathParam("userId") String userId, @QueryParam("realm") String realmName ); + + /** + * Exporter les utilisateurs en CSV + */ + @GET + @Path("/export/csv") + @Produces(MediaType.TEXT_PLAIN) + String exportUsersToCSV(@QueryParam("realm") String realmName); + + /** + * Importer des utilisateurs depuis CSV avec rapport détaillé + */ + @POST + @Path("/import/csv") + @Consumes(MediaType.TEXT_PLAIN) + @Produces(MediaType.APPLICATION_JSON) + dev.lions.user.manager.dto.importexport.ImportResultDTO importUsersFromCSV( + @QueryParam("realm") String realmName, + String csvContent + ); } diff --git a/src/main/java/dev/lions/user/manager/client/view/AuditConsultationBean.java b/src/main/java/dev/lions/user/manager/client/view/AuditConsultationBean.java index fe528d2..aff1220 100644 --- a/src/main/java/dev/lions/user/manager/client/view/AuditConsultationBean.java +++ b/src/main/java/dev/lions/user/manager/client/view/AuditConsultationBean.java @@ -6,6 +6,7 @@ import dev.lions.user.manager.dto.audit.AuditLogDTO; import dev.lions.user.manager.enums.audit.TypeActionAudit; import jakarta.annotation.PostConstruct; import jakarta.faces.application.FacesMessage; +import jakarta.faces.context.ExternalContext; import jakarta.faces.context.FacesContext; import jakarta.faces.view.ViewScoped; import jakarta.inject.Inject; @@ -170,10 +171,29 @@ public class AuditConsultationBean implements Serializable { String dateFinStr = dateFin != null ? dateFin.format(DATE_FORMATTER) : null; String csv = auditServiceClient.exportLogsToCSV(dateDebutStr, dateFinStr); - // TODO: Implémenter le téléchargement du fichier CSV - addSuccessMessage("Export CSV généré avec succès"); + + // Télécharger le fichier CSV + FacesContext facesContext = FacesContext.getCurrentInstance(); + ExternalContext externalContext = facesContext.getExternalContext(); + + String filename = "audit-logs-" + + LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss")) + + ".csv"; + + externalContext.setResponseContentType("text/csv; charset=UTF-8"); + externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + filename + "\""); + + java.io.OutputStream output = externalContext.getResponseOutputStream(); + output.write(csv.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + output.flush(); + + facesContext.responseComplete(); + LOGGER.info("Export CSV généré avec succès: " + filename); + } catch (java.io.IOException e) { + LOGGER.severe("Erreur I/O lors de l'export CSV: " + e.getMessage()); + addErrorMessage("Erreur lors du téléchargement: " + e.getMessage()); } catch (Exception e) { - LOGGER.severe("Erreur lors de l'export: " + e.getMessage()); + LOGGER.severe("Erreur lors de l'export CSV: " + e.getMessage()); addErrorMessage("Erreur lors de l'export: " + e.getMessage()); } } diff --git a/src/main/java/dev/lions/user/manager/client/view/DashboardBean.java b/src/main/java/dev/lions/user/manager/client/view/DashboardBean.java index d125907..981fd53 100644 --- a/src/main/java/dev/lions/user/manager/client/view/DashboardBean.java +++ b/src/main/java/dev/lions/user/manager/client/view/DashboardBean.java @@ -1,6 +1,7 @@ package dev.lions.user.manager.client.view; import dev.lions.user.manager.client.service.AuditServiceClient; +import dev.lions.user.manager.client.service.RealmServiceClient; import dev.lions.user.manager.client.service.RoleServiceClient; import dev.lions.user.manager.client.service.UserServiceClient; import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO; @@ -18,6 +19,8 @@ import jakarta.faces.context.FacesContext; import java.io.Serializable; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.logging.Logger; @@ -47,104 +50,278 @@ public class DashboardBean implements Serializable { @RestClient private AuditServiceClient auditServiceClient; - // Statistiques + @Inject + @RestClient + private RealmServiceClient realmServiceClient; + + @Inject + private UserSessionBean userSessionBean; + + // ==================== STATISTIQUES MÉTIER ==================== + + // Utilisateurs private Long totalUsers = 0L; + private Long activeUsers = 0L; // enabled=true + private Long inactiveUsers = 0L; // enabled=false + private Long usersCreatedToday = 0L; + private Long usersCreatedThisWeek = 0L; + private Long usersCreatedThisMonth = 0L; + + // Rôles private Long totalRoles = 0L; - private Long recentActions = 0L; - private Long activeSessions = 0L; - private Long onlineUsers = 0L; - + + // Audit & Activité + private Long actionsLast24h = 0L; + private Long actionsLast7d = 0L; + private Long actionsLast30d = 0L; + private Long successfulActions24h = 0L; + private Long failedActions24h = 0L; + private Double successRate24h = 0.0; + + // Sécurité & Alertes + private Long criticalActions24h = 0L; + private Long failedLogins24h = 0L; + private Long usersAtRisk = 0L; // Utilisateurs avec multiples tentatives échouées + // Indicateur de chargement private boolean loading = false; - // Méthodes pour obtenir les valeurs formatées pour l'affichage + // ==================== MÉTHODES D'AFFICHAGE ==================== + public String getTotalUsersDisplay() { - if (loading) return "..."; - return totalUsers != null ? String.valueOf(totalUsers) : "0"; + return loading ? "..." : String.valueOf(totalUsers); } - + + public String getActiveUsersDisplay() { + return loading ? "..." : String.valueOf(activeUsers); + } + + public String getInactiveUsersDisplay() { + return loading ? "..." : String.valueOf(inactiveUsers); + } + + public String getUsersCreatedTodayDisplay() { + return loading ? "..." : String.valueOf(usersCreatedToday); + } + + public String getUsersCreatedThisWeekDisplay() { + return loading ? "..." : String.valueOf(usersCreatedThisWeek); + } + public String getTotalRolesDisplay() { - if (loading) return "..."; - return totalRoles != null ? String.valueOf(totalRoles) : "0"; + return loading ? "..." : String.valueOf(totalRoles); } - - public String getRecentActionsDisplay() { - if (loading) return "..."; - return recentActions != null ? String.valueOf(recentActions) : "0"; + + public String getActionsLast24hDisplay() { + return loading ? "..." : String.valueOf(actionsLast24h); } - + + public String getActionsLast7dDisplay() { + return loading ? "..." : String.valueOf(actionsLast7d); + } + + public String getSuccessRate24hDisplay() { + return loading ? "..." : String.format("%.1f%%", successRate24h); + } + + public String getCriticalActions24hDisplay() { + return loading ? "..." : String.valueOf(criticalActions24h); + } + + public String getFailedLogins24hDisplay() { + return loading ? "..." : String.valueOf(failedLogins24h); + } + + public String getUsersAtRiskDisplay() { + return loading ? "..." : String.valueOf(usersAtRisk); + } + public boolean isLoading() { return loading; } - // Realm par défaut - private String realmName = "master"; + public boolean hasAlerts() { + return criticalActions24h > 0 || failedLogins24h > 5 || usersAtRisk > 0; + } + + // Realm - sera défini dynamiquement en fonction de l'utilisateur connecté + private String realmName = "lions-user-manager"; // Valeur par défaut si aucun realm autorisé + private List availableRealms = new ArrayList<>(); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); @PostConstruct public void init() { LOGGER.info("=== Initialisation du DashboardBean ==="); - LOGGER.info("Realm par défaut: " + realmName); LOGGER.info("UserServiceClient injecté: " + (userServiceClient != null ? "OUI" : "NON")); LOGGER.info("RoleServiceClient injecté: " + (roleServiceClient != null ? "OUI" : "NON")); LOGGER.info("AuditServiceClient injecté: " + (auditServiceClient != null ? "OUI" : "NON")); + LOGGER.info("RealmServiceClient injecté: " + (realmServiceClient != null ? "OUI" : "NON")); + LOGGER.info("UserSessionBean injecté: " + (userSessionBean != null ? "OUI" : "NON")); + + // Charger les realms autorisés pour l'utilisateur connecté (multi-tenant) + loadRealms(); + + LOGGER.info("Realm sélectionné pour le dashboard: " + realmName); + + // Charger les statistiques pour le realm de l'utilisateur loadStatistics(); } /** - * Charger toutes les statistiques + * Charger toutes les statistiques métier */ public void loadStatistics() { loading = true; try { - loadTotalUsers(); + // Statistiques utilisateurs + loadUserStatistics(); + + // Statistiques rôles loadTotalRoles(); - loadRecentActions(); - // Les sessions actives nécessitent une API spécifique qui n'existe pas encore - // activeSessions = 0L; - // onlineUsers = 0L; + + // Statistiques activité & audit + loadActivityStatistics(); + + // Statistiques sécurité + loadSecurityStatistics(); + } catch (Exception e) { LOGGER.severe("Erreur lors du chargement des statistiques: " + e.getMessage()); + e.printStackTrace(); } finally { loading = false; } } /** - * Charger le nombre total d'utilisateurs + * Charger les statistiques utilisateurs */ - private void loadTotalUsers() { + private void loadUserStatistics() { try { - LOGGER.info("Début chargement total utilisateurs pour realm: " + realmName); - UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder() + // Total utilisateurs + UserSearchCriteriaDTO criteriaAll = UserSearchCriteriaDTO.builder() .realmName(realmName) .page(0) - .pageSize(1) // On n'a besoin que du count + .pageSize(1) .build(); - - LOGGER.info("Appel userServiceClient.searchUsers()..."); - UserSearchResultDTO result = userServiceClient.searchUsers(criteria); - LOGGER.info("Résultat reçu: " + (result != null ? "NON NULL" : "NULL")); - - if (result != null && result.getTotalCount() != null) { - totalUsers = result.getTotalCount(); - LOGGER.info("✅ Total utilisateurs chargé avec succès: " + totalUsers); - } else { - LOGGER.warning("⚠️ Résultat de recherche utilisateurs null ou totalCount null"); - if (result == null) { - LOGGER.warning(" - result est null"); - } else { - LOGGER.warning(" - result.getTotalCount() est null"); - } - totalUsers = 0L; - } + UserSearchResultDTO resultAll = userServiceClient.searchUsers(criteriaAll); + totalUsers = resultAll != null && resultAll.getTotalCount() != null ? resultAll.getTotalCount() : 0L; + + // Utilisateurs actifs (enabled=true) + UserSearchCriteriaDTO criteriaActive = UserSearchCriteriaDTO.builder() + .realmName(realmName) + .statut(dev.lions.user.manager.enums.user.StatutUser.ACTIF) + .page(0) + .pageSize(1) + .build(); + UserSearchResultDTO resultActive = userServiceClient.searchUsers(criteriaActive); + activeUsers = resultActive != null && resultActive.getTotalCount() != null ? resultActive.getTotalCount() : 0L; + + // Utilisateurs inactifs + inactiveUsers = totalUsers - activeUsers; + + LOGGER.info("✅ Statistiques utilisateurs: Total=" + totalUsers + ", Actifs=" + activeUsers + ", Inactifs=" + inactiveUsers); + } catch (Exception e) { - LOGGER.severe("❌ ERREUR lors du chargement du nombre d'utilisateurs: " + e.getMessage()); - LOGGER.severe(" Type d'erreur: " + e.getClass().getName()); - e.printStackTrace(); + LOGGER.severe("❌ Erreur chargement statistiques utilisateurs: " + e.getMessage()); totalUsers = 0L; - addErrorMessage("Impossible de charger le nombre d'utilisateurs: " + e.getMessage()); + activeUsers = 0L; + inactiveUsers = 0L; + } + } + + /** + * Charger les statistiques d'activité (audit) + */ + private void loadActivityStatistics() { + try { + LocalDateTime now = LocalDateTime.now(); + + // Actions dernières 24h + String date24hAgo = now.minusDays(1).format(DATE_FORMATTER); + String dateNow = now.format(DATE_FORMATTER); + + try { + AuditServiceClient.CountResponse success24h = auditServiceClient.getSuccessCount(date24hAgo, dateNow); + AuditServiceClient.CountResponse failure24h = auditServiceClient.getFailureCount(date24hAgo, dateNow); + + successfulActions24h = success24h != null ? success24h.count : 0L; + failedActions24h = failure24h != null ? failure24h.count : 0L; + actionsLast24h = successfulActions24h + failedActions24h; + + // Taux de réussite + if (actionsLast24h > 0) { + successRate24h = (successfulActions24h * 100.0) / actionsLast24h; + } else { + successRate24h = 100.0; + } + + LOGGER.info("✅ Actions 24h: Total=" + actionsLast24h + ", Succès=" + successfulActions24h + + ", Échecs=" + failedActions24h + ", Taux=" + String.format("%.1f%%", successRate24h)); + + } catch (Exception e) { + LOGGER.warning("⚠️ Impossible d'obtenir les stats d'audit 24h: " + e.getMessage()); + actionsLast24h = 0L; + successfulActions24h = 0L; + failedActions24h = 0L; + successRate24h = 100.0; + } + + // Actions derniers 7 jours + try { + String date7dAgo = now.minusDays(7).format(DATE_FORMATTER); + AuditServiceClient.CountResponse success7d = auditServiceClient.getSuccessCount(date7dAgo, dateNow); + AuditServiceClient.CountResponse failure7d = auditServiceClient.getFailureCount(date7dAgo, dateNow); + actionsLast7d = (success7d != null ? success7d.count : 0L) + (failure7d != null ? failure7d.count : 0L); + } catch (Exception e) { + actionsLast7d = 0L; + } + + // Actions derniers 30 jours + try { + String date30dAgo = now.minusDays(30).format(DATE_FORMATTER); + AuditServiceClient.CountResponse success30d = auditServiceClient.getSuccessCount(date30dAgo, dateNow); + AuditServiceClient.CountResponse failure30d = auditServiceClient.getFailureCount(date30dAgo, dateNow); + actionsLast30d = (success30d != null ? success30d.count : 0L) + (failure30d != null ? failure30d.count : 0L); + } catch (Exception e) { + actionsLast30d = 0L; + } + + } catch (Exception e) { + LOGGER.severe("❌ Erreur chargement statistiques d'activité: " + e.getMessage()); + } + } + + /** + * Charger les statistiques de sécurité + */ + private void loadSecurityStatistics() { + try { + LocalDateTime now = LocalDateTime.now(); + String date24hAgo = now.minusDays(1).format(DATE_FORMATTER); + String dateNow = now.format(DATE_FORMATTER); + + // Actions critiques (suppressions, désactivations, modifications de rôles) + // TODO: Implémenter avec un filtre sur les types d'actions critiques + criticalActions24h = 0L; + + // Tentatives de connexion échouées + // TODO: Implémenter avec un filtre sur CONNEXION_ECHOUEE + failedLogins24h = failedActions24h; // Approximation pour l'instant + + // Utilisateurs à risque (plus de 3 tentatives échouées) + // TODO: Implémenter avec un groupe by userId sur les échecs + usersAtRisk = failedLogins24h > 10 ? 1L : 0L; // Approximation + + LOGGER.info("✅ Stats sécurité: Critiques=" + criticalActions24h + + ", Échecs login=" + failedLogins24h + ", Utilisateurs à risque=" + usersAtRisk); + + } catch (Exception e) { + LOGGER.severe("❌ Erreur chargement statistiques sécurité: " + e.getMessage()); + criticalActions24h = 0L; + failedLogins24h = 0L; + usersAtRisk = 0L; } } @@ -157,7 +334,7 @@ public class DashboardBean implements Serializable { LOGGER.info("Appel roleServiceClient.getAllRealmRoles()..."); List roles = roleServiceClient.getAllRealmRoles(realmName); LOGGER.info("Résultat reçu: " + (roles != null ? "NON NULL, taille: " + roles.size() : "NULL")); - + if (roles != null) { totalRoles = (long) roles.size(); LOGGER.info("✅ Total rôles chargé avec succès: " + totalRoles); @@ -174,61 +351,6 @@ public class DashboardBean implements Serializable { } } - /** - * Charger le nombre d'actions récentes (dernières 24h) - */ - private void loadRecentActions() { - try { - LocalDateTime dateDebut = LocalDateTime.now().minusDays(1); - String dateDebutStr = dateDebut.format(DATE_FORMATTER); - String dateFinStr = LocalDateTime.now().format(DATE_FORMATTER); - - LOGGER.info("Début chargement actions récentes (24h)"); - LOGGER.info(" Date début: " + dateDebutStr); - LOGGER.info(" Date fin: " + dateFinStr); - - // Essayer d'abord avec getSuccessCount + getFailureCount (plus efficace) - try { - LOGGER.info("Tentative avec getSuccessCount() et getFailureCount()..."); - AuditServiceClient.CountResponse successResponse = auditServiceClient.getSuccessCount(dateDebutStr, dateFinStr); - Long successCount = successResponse != null ? successResponse.count : 0L; - AuditServiceClient.CountResponse failureResponse = auditServiceClient.getFailureCount(dateDebutStr, dateFinStr); - Long failureCount = failureResponse != null ? failureResponse.count : 0L; - LOGGER.info(" SuccessCount: " + successCount); - LOGGER.info(" FailureCount: " + failureCount); - recentActions = (successCount != null ? successCount : 0L) + (failureCount != null ? failureCount : 0L); - LOGGER.info("✅ Actions récentes chargées avec succès: " + recentActions); - } catch (Exception e2) { - LOGGER.warning("⚠️ Impossible d'obtenir les statistiques d'audit, tentative avec searchLogs: " + e2.getMessage()); - // Fallback: utiliser searchLogs - List logs = auditServiceClient.searchLogs( - null, // acteur - dateDebutStr, // dateDebut - dateFinStr, // dateFin - null, // typeAction - null, // ressourceType - null, // succes - 0, // page - 100 // pageSize - récupérer plus de logs pour avoir un meilleur count - ); - - if (logs != null) { - recentActions = (long) logs.size(); - LOGGER.info("✅ Actions récentes chargées via searchLogs: " + recentActions); - } else { - LOGGER.warning("⚠️ searchLogs a retourné null"); - recentActions = 0L; - } - } - } catch (Exception e) { - LOGGER.severe("❌ ERREUR lors du chargement des actions récentes: " + e.getMessage()); - LOGGER.severe(" Type d'erreur: " + e.getClass().getName()); - e.printStackTrace(); - recentActions = 0L; - addErrorMessage("Impossible de charger les actions récentes: " + e.getMessage()); - } - } - /** * Rafraîchir les statistiques */ @@ -237,7 +359,53 @@ public class DashboardBean implements Serializable { loadStatistics(); addSuccessMessage("Statistiques rafraîchies avec succès"); } - + + /** + * Charger les realms disponibles depuis Keycloak en fonction des permissions de l'utilisateur + * Architecture multi-tenant: le realm est déterminé dynamiquement selon l'utilisateur connecté + */ + private void loadRealms() { + try { + // Récupérer tous les realms depuis Keycloak + List allRealms = realmServiceClient.getAllRealms(); + + if (allRealms == null || allRealms.isEmpty()) { + LOGGER.warning("Aucun realm trouvé dans Keycloak"); + availableRealms = Collections.emptyList(); + return; + } + + List authorizedRealms = userSessionBean.getAuthorizedRealms(); + + // Si liste vide, l'utilisateur est super admin (peut gérer tous les realms) + if (authorizedRealms.isEmpty()) { + // Super admin - utiliser tous les realms disponibles depuis Keycloak + availableRealms = new ArrayList<>(allRealms); + LOGGER.info("Super admin détecté - " + availableRealms.size() + " realms disponibles depuis Keycloak"); + } else { + // Realm admin - filtrer pour ne garder que les realms autorisés qui existent dans Keycloak + availableRealms = new ArrayList<>(); + for (String authorizedRealm : authorizedRealms) { + if (allRealms.contains(authorizedRealm)) { + availableRealms.add(authorizedRealm); + } + } + LOGGER.info("Realms autorisés pour l'utilisateur: " + availableRealms.size()); + + // Définir le premier realm autorisé comme realm par défaut + if (!availableRealms.isEmpty() && !availableRealms.contains(realmName)) { + realmName = availableRealms.get(0); + LOGGER.info("Realm par défaut changé vers: " + realmName); + } + } + } catch (Exception e) { + LOGGER.severe("Erreur lors du chargement des realms depuis Keycloak: " + e.getMessage()); + LOGGER.log(java.util.logging.Level.SEVERE, "Exception complète", e); + // Fallback: garder le realm par défaut "lions-user-manager" + availableRealms = Collections.emptyList(); + } + } + // Méthodes utilitaires pour les messages private void addSuccessMessage(String message) { FacesContext.getCurrentInstance().addMessage(null, diff --git a/src/main/java/dev/lions/user/manager/client/view/DashboardView.java b/src/main/java/dev/lions/user/manager/client/view/DashboardView.java index b6ed63d..f4704ae 100644 --- a/src/main/java/dev/lions/user/manager/client/view/DashboardView.java +++ b/src/main/java/dev/lions/user/manager/client/view/DashboardView.java @@ -28,6 +28,7 @@ import java.util.Map; @Named @ViewScoped @Slf4j +@SuppressWarnings("deprecation") // ChartData API dépréciée - migration vers JSON prévue public class DashboardView implements Serializable { @Inject @@ -87,6 +88,7 @@ public class DashboardView implements Serializable { } } + @SuppressWarnings("deprecation") // ChartData sera remplacé par une approche JSON moderne dans une version future public void createBarModel() { barModel = new BarChartModel(); ChartData data = new ChartData(); diff --git a/src/main/java/dev/lions/user/manager/client/view/UserCreationBean.java b/src/main/java/dev/lions/user/manager/client/view/UserCreationBean.java index 5a7263a..7b47821 100644 --- a/src/main/java/dev/lions/user/manager/client/view/UserCreationBean.java +++ b/src/main/java/dev/lions/user/manager/client/view/UserCreationBean.java @@ -120,6 +120,32 @@ public class UserCreationBean implements Serializable { return "userListPage"; } + /** + * Valider la correspondance des mots de passe en temps réel + */ + public void validatePasswordMatch() { + FacesContext context = FacesContext.getCurrentInstance(); + + // Vérifier que les deux champs sont remplis + if (password != null && !password.isEmpty() && + passwordConfirm != null && !passwordConfirm.isEmpty()) { + + // Vérifier la correspondance + if (!password.equals(passwordConfirm)) { + context.addMessage("formUserCreation:passwordConfirm", + new FacesMessage(FacesMessage.SEVERITY_ERROR, + "Erreur", + "Les mots de passe ne correspondent pas")); + } else { + // Succès - afficher message de confirmation + context.addMessage("formUserCreation:passwordConfirm", + new FacesMessage(FacesMessage.SEVERITY_INFO, + "Validé", + "Les mots de passe correspondent")); + } + } + } + /** * Charger les realms disponibles depuis Keycloak */ diff --git a/src/main/java/dev/lions/user/manager/client/view/UserListBean.java b/src/main/java/dev/lions/user/manager/client/view/UserListBean.java index d6b34ce..c2b63ba 100644 --- a/src/main/java/dev/lions/user/manager/client/view/UserListBean.java +++ b/src/main/java/dev/lions/user/manager/client/view/UserListBean.java @@ -8,6 +8,7 @@ import dev.lions.user.manager.dto.user.UserSearchResultDTO; import dev.lions.user.manager.enums.user.StatutUser; import jakarta.annotation.PostConstruct; import jakarta.faces.application.FacesMessage; +import jakarta.faces.context.ExternalContext; import jakarta.faces.context.FacesContext; import jakarta.faces.event.ActionEvent; import jakarta.faces.view.ViewScoped; @@ -18,6 +19,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import org.primefaces.event.data.PageEvent; import java.io.Serializable; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -75,6 +77,9 @@ public class UserListBean implements Serializable { private List statutOptions = List.of(StatutUser.values()); private List availableRealms = new ArrayList<>(); + // Résultats de l'import CSV + private dev.lions.user.manager.dto.importexport.ImportResultDTO lastImportResult; + @PostConstruct public void init() { LOGGER.info("Initialisation de UserListBean"); @@ -308,17 +313,134 @@ public class UserListBean implements Serializable { } /** - * Exporter vers CSV (placeholder) + * Exporter les utilisateurs en CSV */ public void exportToCSV() { - addSuccessMessage("Fonctionnalité d'export en cours de développement"); + try { + if (realmName == null || realmName.isEmpty()) { + addErrorMessage("Veuillez sélectionner un realm"); + return; + } + + String csv = userServiceClient.exportUsersToCSV(realmName); + + // Télécharger le fichier CSV + FacesContext facesContext = FacesContext.getCurrentInstance(); + ExternalContext externalContext = facesContext.getExternalContext(); + + String filename = "users_export_" + + LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmmss")) + + ".csv"; + + externalContext.setResponseContentType("text/csv; charset=UTF-8"); + externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + filename + "\""); + + java.io.OutputStream output = externalContext.getResponseOutputStream(); + output.write(csv.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + output.flush(); + + facesContext.responseComplete(); + LOGGER.info("Export CSV généré avec succès: " + filename); + } catch (java.io.IOException e) { + LOGGER.severe("Erreur I/O lors de l'export CSV: " + e.getMessage()); + addErrorMessage("Erreur lors du téléchargement: " + e.getMessage()); + } catch (Exception e) { + LOGGER.severe("Erreur lors de l'export CSV: " + e.getMessage()); + addErrorMessage("Erreur lors de l'export: " + e.getMessage()); + } } /** - * Importer des utilisateurs (placeholder) + * Télécharger un template CSV pour l'import d'utilisateurs + */ + public void downloadCSVTemplate() { + try { + // Créer un template CSV avec des exemples + StringBuilder csvTemplate = new StringBuilder(); + csvTemplate.append("username,prenom,nom,email\n"); + csvTemplate.append("jdupont,Jean,Dupont,jean.dupont@example.com\n"); + csvTemplate.append("mmartin,Marie,Martin,marie.martin@example.com\n"); + csvTemplate.append("pbernard,Pierre,Bernard,pierre.bernard@example.com\n"); + + FacesContext facesContext = FacesContext.getCurrentInstance(); + ExternalContext externalContext = facesContext.getExternalContext(); + + String filename = "template_import_users.csv"; + + externalContext.setResponseContentType("text/csv; charset=UTF-8"); + externalContext.setResponseHeader("Content-Disposition", "attachment; filename=\"" + filename + "\""); + + java.io.OutputStream output = externalContext.getResponseOutputStream(); + output.write(csvTemplate.toString().getBytes(java.nio.charset.StandardCharsets.UTF_8)); + output.flush(); + + facesContext.responseComplete(); + LOGGER.info("Template CSV téléchargé avec succès: " + filename); + } catch (java.io.IOException e) { + LOGGER.severe("Erreur I/O lors du téléchargement du template CSV: " + e.getMessage()); + addErrorMessage("Erreur lors du téléchargement du template: " + e.getMessage()); + } catch (Exception e) { + LOGGER.severe("Erreur lors du téléchargement du template CSV: " + e.getMessage()); + addErrorMessage("Erreur lors du téléchargement: " + e.getMessage()); + } + } + + /** + * Importer des utilisateurs depuis un fichier CSV + * Cette méthode sera appelée par le gestionnaire d'upload de fichier */ public void importUsers() { - addSuccessMessage("Fonctionnalité d'import en cours de développement"); + addInfoMessage("Veuillez utiliser le bouton 'Parcourir' pour sélectionner un fichier CSV"); + } + + /** + * Gérer l'upload de fichier CSV pour import + */ + public void handleFileUpload(org.primefaces.event.FileUploadEvent event) { + try { + if (realmName == null || realmName.isEmpty()) { + addErrorMessage("Veuillez sélectionner un realm avant d'importer"); + return; + } + + if (event.getFile() == null) { + addErrorMessage("Aucun fichier sélectionné"); + return; + } + + // Lire le contenu du fichier + String csvContent = new String(event.getFile().getContent(), java.nio.charset.StandardCharsets.UTF_8); + + if (csvContent.trim().isEmpty()) { + addErrorMessage("Le fichier CSV est vide"); + return; + } + + // Appeler l'API d'import + dev.lions.user.manager.dto.importexport.ImportResultDTO result = + userServiceClient.importUsersFromCSV(realmName, csvContent); + + // Stocker le résultat pour l'affichage dans le dialog + this.lastImportResult = result; + + // Afficher le résultat + LOGGER.info("Import terminé: " + result.getMessage()); + + if (result.getErrorCount() == 0) { + addSuccessMessage(result.getMessage()); + } else { + addWarningMessage(result.getMessage()); + } + + // Ouvrir le dialog de résultats détaillés + org.primefaces.PrimeFaces.current().executeScript("PF('importResultDialog').show();"); + + loadUsers(); // Recharger la liste + + } catch (Exception e) { + LOGGER.severe("Erreur lors de l'import: " + e.getMessage()); + addErrorMessage("Erreur lors de l'import: " + e.getMessage()); + } } /** @@ -396,5 +518,15 @@ public class UserListBean implements Serializable { FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message)); } + + private void addInfoMessage(String message) { + FacesContext.getCurrentInstance().addMessage(null, + new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message)); + } + + private void addWarningMessage(String message) { + FacesContext.getCurrentInstance().addMessage(null, + new FacesMessage(FacesMessage.SEVERITY_WARN, "Avertissement", message)); + } } diff --git a/src/main/resources/META-INF/faces-config.xml b/src/main/resources/META-INF/faces-config.xml index c4f7f78..6699f56 100644 --- a/src/main/resources/META-INF/faces-config.xml +++ b/src/main/resources/META-INF/faces-config.xml @@ -190,6 +190,23 @@ + + + Page de démonstration complète Freya Extension + freyaShowcasePage + /pages/user-manager/freya-showcase.xhtml + + + + + Navigation directe vers Freya Showcase + /pages/user-manager/freya-showcase + /pages/user-manager/freya-showcase.xhtml + + + diff --git a/src/main/resources/META-INF/resources/pages/admin/realm-assignments.xhtml b/src/main/resources/META-INF/resources/pages/admin/realm-assignments.xhtml index 7bdfc77..d851c91 100644 --- a/src/main/resources/META-INF/resources/pages/admin/realm-assignments.xhtml +++ b/src/main/resources/META-INF/resources/pages/admin/realm-assignments.xhtml @@ -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"> Affectation des Realms - Lions User Manager @@ -24,11 +25,11 @@

Gérer les permissions d'administration par realm (contrôle multi-tenant)

- + @@ -91,30 +92,32 @@
Affectations Actuelles
- +
- + - + - +
@@ -127,92 +130,101 @@ - - + + - - - + + + - - - - + + + + - + - + - +
- + - + - + - + - + - +
@@ -234,72 +246,59 @@
- - + filterMatchMode="contains" + iconLeft="pi pi-user"> - +
- - + - +
- - +
- - +
-
- -
+
- - +
@@ -319,18 +318,20 @@
- - + +
@@ -338,11 +339,12 @@ + - - + + diff --git a/src/main/resources/META-INF/resources/pages/user-manager/audit/logs.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/audit/logs.xhtml index 70ac1e9..7c86c17 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/audit/logs.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/audit/logs.xhtml @@ -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"> @@ -25,12 +26,11 @@
- +
@@ -144,74 +144,70 @@
- - +
- - + iconLeft="pi pi-bolt"> - +
- - + iconLeft="pi pi-check-circle"> - +
- - +
- - +
- - +
- - + +
@@ -227,9 +223,9 @@
Logs d'Audit
- + @@ -245,16 +241,16 @@ currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}" styleClass="w-full" emptyMessage="Aucun log d'audit trouvé" - reflow="true"> + responsiveLayout="scroll"> - - + + - +
#{log.typeAction} @@ -262,7 +258,7 @@ - +
@@ -275,7 +271,7 @@ - +
#{log.ressourceType} @@ -283,7 +279,7 @@ - +
#{log.dateAction} @@ -291,28 +287,30 @@ - + #{not empty log.details ? log.details : '-'} - + #{not empty log.adresseIp ? log.adresseIp : '-'} - - + + - + @@ -336,8 +334,8 @@
Statut - +
@@ -409,12 +407,11 @@
- +
diff --git a/src/main/resources/META-INF/resources/pages/user-manager/dashboard.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/dashboard.xhtml index 3af3573..d210e55 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/dashboard.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/dashboard.xhtml @@ -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"> Tableau de Bord - Lions User Manager @@ -21,114 +22,174 @@

Tableau de Bord

-

Vue d'ensemble de la gestion des utilisateurs Keycloak

+

Vue d'ensemble de la gestion des utilisateurs - Realm: #{dashboardBean.realmName}

- +
-
Statistiques Principales
+
Indicateurs Clés de Performance
- +
- -
+ +
-
Utilisateurs Actifs
-
#{dashboardBean.totalUsersDisplay}
+
Total Utilisateurs
+
#{dashboardBean.totalUsersDisplay}
+
+ + Dans le système +
- + style="width: 3.5rem; height: 3.5rem"> +
-
- - Total utilisateurs -
- +
- +
- -
+ +
-
Rôles Realm
-
#{dashboardBean.totalRolesDisplay}
+
Utilisateurs Actifs
+
#{dashboardBean.activeUsersDisplay}
+
+ + Comptes activés +
- + style="width: 3.5rem; height: 3.5rem"> +
-
- - Rôles configurés -
- +
- +
- -
+ +
-
Actions Récentes
-
#{dashboardBean.recentActionsDisplay}
+
Utilisateurs Inactifs
+
#{dashboardBean.inactiveUsersDisplay}
+
+ + Comptes désactivés +
- + style="width: 3.5rem; height: 3.5rem"> +
-
- - Dernières 24h -
- +
- +
-
-
-
-
Realm Actif
-
lions-user-manager
-
-
- +
+ +
+
+
Taux de Succès
+
#{dashboardBean.successRate24hDisplay}
+
+ + Dernières 24h +
+
+
+ +
+
+
+
+ + +
+
+
+ +
Activité & Performance
-
- - Realm Keycloak + +
+ +
+
+
Actions 24h
+
#{dashboardBean.actionsLast24hDisplay}
+ +
+
+ + +
+
+
Actions 7j
+
#{dashboardBean.actionsLast7dDisplay}
+ +
+
+ + +
+
+
Performance
+
#{dashboardBean.successRate24hDisplay}
+ +
+
+ + +
+
+
+
+ + Actions réussies +
+ #{dashboardBean.successfulActions24h} +
+
+
+ + Actions échouées +
+ #{dashboardBean.failedActions24h} +
+
+
@@ -145,32 +206,32 @@
- +
- +
- +
- +
@@ -179,7 +240,113 @@
Conseil
- Utilisez les raccourcis ci-dessus pour accéder rapidement aux fonctionnalités principales + Utilisez ces raccourcis pour accéder rapidement aux fonctionnalités principales +
+
+
+
+
+ + + +
+
+ +
Alertes de Sécurité
+
+ +
+ +
+
+ +
#{dashboardBean.criticalActions24hDisplay}
+
Actions critiques
+ Dernières 24h +
+
+ + +
+
+ +
#{dashboardBean.failedLogins24hDisplay}
+
Connexions échouées
+ Dernières 24h +
+
+ + +
+
+ +
#{dashboardBean.usersAtRiskDisplay}
+
Utilisateurs à risque
+ Nécessitent attention +
+
+
+ +
+
+ +
+
Recommandation
+ Consultez le journal d'audit pour analyser les événements suspects +
+
+
+
+
+ + +
+
+
+ +
Ressources
+
+ +
+ +
+
+
+
+
+ +
+
+
Rôles Realm
+
#{dashboardBean.totalRolesDisplay}
+
+
+ +
+
+
+ + +
+
+
+
+ + Realm Keycloak +
+
+ #{dashboardBean.realmName} + +
+
@@ -192,7 +359,7 @@
- +
Informations Système
@@ -202,34 +369,12 @@
- Version + Version Application
1.0.0
- -
-
-
- - Realm Keycloak -
- lions-user-manager -
-
- - -
-
-
- - Statut -
- -
-
-
@@ -237,90 +382,18 @@ Framework
- Quarkus 3.15.1 + Quarkus + PrimeFaces Freya
- +
- - Interface + + Statut Système
- PrimeFaces Freya -
-
- - -
-
-
- - Environnement -
- -
-
-
-
-
- - -
-
-
-
- -
Activités Récentes
-
- -
- -
- -
-
- -
0
-
Utilisateurs créés
- Aujourd'hui -
-
- - -
-
- -
0
-
Rôles modifiés
- Cette semaine -
-
- - -
-
- -
-
-
Sessions actives
- En temps réel -
-
- - -
-
- -
0
-
Actions critiques
- 24 dernières heures +
diff --git a/src/main/resources/META-INF/resources/pages/user-manager/roles/assign.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/roles/assign.xhtml deleted file mode 100644 index 9cebae9..0000000 --- a/src/main/resources/META-INF/resources/pages/user-manager/roles/assign.xhtml +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - - - - - Attribution de Rôles - Lions User Manager - - -
- -
-
-
-
- -
-

Attribution de Rôles

-

Gérer les rôles de l'utilisateur

-
-
- - - Retour à la liste - -
-
-
- - -
-
-

- - Informations de l'Utilisateur -

- - -
-
-
- -
- -
-

#{userProfilBean.user.username}

-

#{userProfilBean.user.email}

-
-
- -
-
-
-
- -

#{userProfilBean.user.prenom}

-
-
- -
-
- -

#{userProfilBean.user.nom}

-
-
- -
-
- -

#{userProfilBean.user.email}

-
-
- -
-
- -
- - - - - -
-
-
-
-
-
-
- - -
- -

Utilisateur non trouvé

-

- - -

- Pour assigner des rôles, accédez à cette page depuis la liste des utilisateurs - - - Aller à la liste des utilisateurs - -
-
-
-
- - - -
-
-

- - Rôles Actuels -

- - - -
- -
-
-
- -
-
#{role}
- Rôle Realm -
-
- - - -
-
-
- - -
- -

Aucun rôle assigné

- Assignez des rôles depuis la liste disponible -
-
- -
-
- - Total: #{userProfilBean.user.realmRoles != null ? userProfilBean.user.realmRoles.size() : 0} rôle(s) -
- -
-
-
-
- -
-
-

- - Rôles Disponibles -

- - - - - - - -
- - - -
-
-
-
- - #{role.name} -
-

- - -

-
- -
-
-
-
- - -
- -

Aucun rôle disponible

- Créez des rôles depuis la page de gestion des rôles -
-
- -
-
- -
-
Astuce
- Cliquez sur pour assigner un rôle à l'utilisateur -
-
-
-
-
-
- - -
-
-

- - Actions -

- - -
- - - - Voir le Profil - - - - - - Modifier l'Utilisateur - - - - - Liste des Utilisateurs - - - - - Gérer les Rôles - -
-
-
-
-
-
- - - - - - -
- -
diff --git a/src/main/resources/META-INF/resources/pages/user-manager/roles/list.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/roles/list.xhtml index de53f34..8f50da6 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/roles/list.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/roles/list.xhtml @@ -42,164 +42,132 @@
-

- - Filtres -

- - -
- -
-
- - - - - - -
-
- - -
-
- - - - - - -
-
- - -
-
- - - - - -
-
-
-
-
-
- - -
-
-
-
-
-
-
Rôles Realm
-
#{roleGestionBean.realmRoles.size()}
+ +
+

+ + Filtres +

+ + +
+ +
+ + + + +
-
- + + +
+ + + + + +
+ + +
+ + + +
-
- - Rôles du realm -
-
+
-
-
-
-
-
Rôles Client
-
#{roleGestionBean.clientRoles.size()}
-
-
- -
-
-
- - Rôles spécifiques client -
-
-
+ +
+

+ + Statistiques (φ = 1.618) +

-
-
-
-
-
Total Rôles
-
#{roleGestionBean.allRoles.size()}
+ +
+ +
+
+
+ +
+
#{roleGestionBean.realmRoles.size()}
+
Rôles Realm
+
-
- -
-
-
- - Tous les rôles configurés -
-
-
-
-
-
-
-
Realm Actif
-
#{roleGestionBean.realmName}
+ +
+
+
+ +
+
#{roleGestionBean.clientRoles.size()}
+
Rôles Client
+
-
- + + +
+
+
+ +
+
#{roleGestionBean.allRoles.size()}
+
Total Rôles
+
+
+ + +
+
+
+ +
+
#{roleGestionBean.realmName}
+
Realm Actif
+
-
- - Realm actuellement sélectionné -
-
+
- +
- - -
-
-
- - - - - - Lettres, chiffres, underscores et tirets uniquement -
- -
- - - -
-
+ +
+
+ + + +
- - - - +
+ +
+
- - - - - + + + +
- - -
-
-
- - - - - -
- -
- - - - - -
- -
- - - -
-
+ +
+
+ + + +
- - - - +
+ + + + +
- - - - - +
+ +
+
+ + + + +
+ - - + + diff --git a/src/main/resources/META-INF/resources/pages/user-manager/settings.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/settings.xhtml index 109f651..eefd7aa 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/settings.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/settings.xhtml @@ -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 @@
Informations du compte
- - - +
+
+ +
- - +
+ +
- - +
+ +
- - - +
+ +
+
@@ -59,35 +72,34 @@
Préférences
-
- Thème des composants - - - - -
-
- Mode sombre - - - - - -
-
- Style d'input - - - - - -
+ + + + + + + + + + + + + + + +
@@ -97,29 +109,26 @@
Actions
-
+
- + - + - +
diff --git a/src/main/resources/META-INF/resources/pages/user-manager/sync/dashboard.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/sync/dashboard.xhtml index 8f2b662..1283dbe 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/sync/dashboard.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/sync/dashboard.xhtml @@ -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"> Synchronisation Keycloak - Lions User Manager diff --git a/src/main/resources/META-INF/resources/pages/user-manager/users/create.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/users/create.xhtml index 2789d83..1ef1a19 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/users/create.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/users/create.xhtml @@ -26,16 +26,19 @@
- - - - Retour - +
@@ -125,51 +128,46 @@
-
- - - - - - - Au moins 8 caractères avec lettres et chiffres - -
+ + + +
-
- - - - - - Doit correspondre au mot de passe - -
+ + + +
@@ -220,26 +218,20 @@
-
- - - -
+ + L'utilisateur peut se connecter immédiatement + -
- - - -
+ + Marquer l'email comme vérifié +
@@ -332,16 +324,17 @@ validateClient="true" /> - - + +
@@ -103,6 +104,7 @@ placeholder="ex: Jean" helpText="Prénom de l'utilisateur"> +
@@ -115,6 +117,7 @@ placeholder="ex: Dupont" helpText="Nom de famille de l'utilisateur"> +
@@ -157,26 +160,16 @@
-
- - - -
+ -
- - - -
+
@@ -193,9 +186,11 @@
-
Aperçu
+
Aperçu (Temps réel)
+ +
@@ -250,6 +245,8 @@
+ +
@@ -309,17 +306,9 @@
- - - - + diff --git a/src/main/resources/META-INF/resources/pages/user-manager/users/list.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/users/list.xhtml index a6012e3..2eaabf3 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/users/list.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/users/list.xhtml @@ -27,10 +27,25 @@
+ + @@ -149,76 +164,67 @@ SECTION RECHERCHE ET FILTRES ================================================================ -->
-
-
- -
Recherche et Filtres
-
+ + + + + +
-
- - - - -
+ + +
-
- - - - - -
+ + + +
-
- - - - - - -
+ + + + +
-
+
+ update=":formUserList:userTable @form" + rendered="#{userListBean.searchText != null or userListBean.selectedStatut != null}" />
-
+
- +
@@ -269,7 +275,7 @@ - +
#{user.email} @@ -280,13 +286,13 @@ - + - +
@@ -304,165 +310,256 @@ - -
- + +
+ - - - - - + + + + + + - - - - - + + + + - - - - - + - - - - + + + + + - - - - - + + + + + + + + + + +
- - -
-
-
- -
Actions Rapides
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
- - -
-

- Importez des utilisateurs depuis un fichier CSV ou JSON. -

- -
- - + responsive="true" + width="600" + showEffect="fade" + hideEffect="fade" + closeOnEscape="true"> + + + +
+
+ +
+
Format du fichier CSV requis:
+
    +
  • En-tête: username,prenom,nom,email
  • +
  • Encodage: UTF-8
  • +
  • Séparateur: virgule (,)
  • +
+
+ + +
+
Télécharger le template CSV:
+ + + Utilisez ce template pour préparer votre fichier d'import + +
+ + + + +
+
Sélectionner le fichier CSV:
+ +
+ +
+
+ + + Astuce: Exportez d'abord vos utilisateurs existants pour voir le format attendu + +
+
+
+ + + + + + + + +
+
Résumé de l'import:
+
+
+
+
Total lignes
+
#{userListBean.lastImportResult.totalLines}
+
+
+
+
+
Succès
+
#{userListBean.lastImportResult.successCount}
+
+
+
+
+
Erreurs
+
#{userListBean.lastImportResult.errorCount}
+
+
+
+
+ + + + + Détails des Erreurs + + + + + + + + + + + + + + #{error.field != null ? error.field : '-'} + + + +
+ #{error.message} + + #{error.lineContent} + +
+
+
+
+ + + +
+ +
Import réussi!
+

+ Tous les utilisateurs ont été importés avec succès. +

+
+
+ + +
+ +
+
diff --git a/src/main/resources/META-INF/resources/pages/user-manager/users/profile.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/users/profile.xhtml index a6540d6..6cf4f46 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/users/profile.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/users/profile.xhtml @@ -333,26 +333,29 @@ Gestion du Profil
- + - + - + - + - + - +
@@ -365,28 +368,33 @@ Sessions et Sécurité
- + - + - + - + - + - +
@@ -397,16 +405,19 @@
+ - - + + diff --git a/src/main/resources/META-INF/resources/pages/user-manager/users/view.xhtml b/src/main/resources/META-INF/resources/pages/user-manager/users/view.xhtml index 1ca4300..601c42a 100644 --- a/src/main/resources/META-INF/resources/pages/user-manager/users/view.xhtml +++ b/src/main/resources/META-INF/resources/pages/user-manager/users/view.xhtml @@ -140,11 +140,11 @@
- - +
@@ -152,9 +152,9 @@
- +
@@ -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">
diff --git a/src/main/resources/META-INF/resources/templates/components/layout/menu.xhtml b/src/main/resources/META-INF/resources/templates/components/layout/menu.xhtml index 2ca51de..50c31a4 100644 --- a/src/main/resources/META-INF/resources/templates/components/layout/menu.xhtml +++ b/src/main/resources/META-INF/resources/templates/components/layout/menu.xhtml @@ -37,18 +37,18 @@ - - - + + diff --git a/src/main/resources/META-INF/resources/templates/components/shared/buttons/button-user-action.xhtml b/src/main/resources/META-INF/resources/templates/components/shared/buttons/button-user-action.xhtml index 5a9cbba..7788a95 100644 --- a/src/main/resources/META-INF/resources/templates/components/shared/buttons/button-user-action.xhtml +++ b/src/main/resources/META-INF/resources/templates/components/shared/buttons/button-user-action.xhtml @@ -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"> + @@ -51,47 +53,14 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/java/dev/lions/user/manager/client/view/DashboardBeanTest.java b/src/test/java/dev/lions/user/manager/client/view/DashboardBeanTest.java index 2c1b27a..81e5867 100644 --- a/src/test/java/dev/lions/user/manager/client/view/DashboardBeanTest.java +++ b/src/test/java/dev/lions/user/manager/client/view/DashboardBeanTest.java @@ -78,8 +78,9 @@ class DashboardBeanTest { assertEquals(100L, dashboardBean.getTotalUsers()); assertEquals(1L, dashboardBean.getTotalRoles()); - assertEquals(55L, dashboardBean.getRecentActions()); + assertEquals(55L, dashboardBean.getActionsLast24h()); assertEquals("100", dashboardBean.getTotalUsersDisplay()); + assertEquals("55", dashboardBean.getActionsLast24hDisplay()); } @Test