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:
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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();
|
||||
UserSearchResultDTO resultAll = userServiceClient.searchUsers(criteriaAll);
|
||||
totalUsers = resultAll != null && resultAll.getTotalCount() != null ? resultAll.getTotalCount() : 0L;
|
||||
|
||||
LOGGER.info("Appel userServiceClient.searchUsers()...");
|
||||
UserSearchResultDTO result = userServiceClient.searchUsers(criteria);
|
||||
LOGGER.info("Résultat reçu: " + (result != null ? "NON NULL" : "NULL"));
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -238,6 +360,52 @@ public class DashboardBean implements Serializable {
|
||||
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<String> allRealms = realmServiceClient.getAllRealms();
|
||||
|
||||
if (allRealms == null || allRealms.isEmpty()) {
|
||||
LOGGER.warning("Aucun realm trouvé dans Keycloak");
|
||||
availableRealms = Collections.emptyList();
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> 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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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<StatutUser> statutOptions = List.of(StatutUser.values());
|
||||
private List<String> 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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é & 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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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 && !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 && !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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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="Email vérifié"
|
||||
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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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é" -->
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
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>
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
<!-- 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 -->
|
||||
@@ -45,10 +44,11 @@
|
||||
<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')}">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user