refactoring
This commit is contained in:
@@ -1,130 +0,0 @@
|
||||
# ============================================================================
|
||||
# Lions User Manager - Client Quarkus PrimeFaces Freya - .gitignore
|
||||
# ============================================================================
|
||||
|
||||
# Maven
|
||||
target/
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
# Build artifacts
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
*.class
|
||||
*.idx
|
||||
|
||||
# Eclipse
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
.metadata/
|
||||
bin/
|
||||
|
||||
# IntelliJ IDEA
|
||||
.idea/
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
out/
|
||||
|
||||
# NetBeans
|
||||
nbproject/
|
||||
nbbuild/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
*.code-workspace
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# Windows
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
*.log.*
|
||||
hs_err_pid*.log
|
||||
|
||||
# Quarkus
|
||||
.quarkus/
|
||||
quarkus-app/
|
||||
quarkus-run.jar
|
||||
quarkus-*.dat
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
*~
|
||||
*.orig
|
||||
|
||||
# Test files and reports
|
||||
test_output*.txt
|
||||
surefire-reports/
|
||||
failsafe-reports/
|
||||
*.dump
|
||||
*.dumpstream
|
||||
|
||||
# Test coverage
|
||||
.jacoco/
|
||||
jacoco.exec
|
||||
coverage/
|
||||
target/site/jacoco/
|
||||
|
||||
# Application specific
|
||||
application-local.properties
|
||||
application-*.local.properties
|
||||
|
||||
# Configuration files with sensitive data
|
||||
*.local.json
|
||||
|
||||
# Token and authentication files
|
||||
token.json
|
||||
token.txt
|
||||
*.token
|
||||
|
||||
# Generated sources
|
||||
generated-sources/
|
||||
generated-test-sources/
|
||||
|
||||
# Maven status
|
||||
maven-status/
|
||||
|
||||
# Node modules (si utilisé pour le build frontend)
|
||||
node_modules/
|
||||
|
||||
# Build metrics
|
||||
build-metrics.json
|
||||
|
||||
# Quarkus Dev Services
|
||||
.devservices/
|
||||
|
||||
# IDE specific
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
|
||||
# OS specific
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
</parent>
|
||||
|
||||
<artifactId>lions-user-manager-client-quarkus-primefaces-freya</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Lions User Manager - Client (Quarkus + PrimeFaces Freya)</name>
|
||||
@@ -22,7 +21,6 @@
|
||||
<dependency>
|
||||
<groupId>dev.lions.user.manager</groupId>
|
||||
<artifactId>lions-user-manager-server-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Quarkus Extensions -->
|
||||
@@ -42,6 +40,11 @@
|
||||
<artifactId>quarkus-oidc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-rest-client-oidc-token-propagation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-security</artifactId>
|
||||
@@ -67,12 +70,6 @@
|
||||
<version>5.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- PrimeFaces Freya Extension -->
|
||||
<dependency>
|
||||
<groupId>dev.lions</groupId>
|
||||
<artifactId>primefaces-freya-extension</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Quarkus OmniFaces Extension (optional but recommended) -->
|
||||
<dependency>
|
||||
<groupId>io.quarkiverse.omnifaces</groupId>
|
||||
@@ -100,28 +97,26 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5-mockito</artifactId>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5-mockito</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -1,52 +1,55 @@
|
||||
package dev.lions.user.manager.client.api;
|
||||
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.dto.audit.AuditLogDTO;
|
||||
import dev.lions.user.manager.enums.audit.TypeActionAudit;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RegisterRestClient(configKey = "user-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/audit")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface AuditRestClient {
|
||||
|
||||
@GET
|
||||
List<AuditLogDTO> searchLogs(
|
||||
@QueryParam("acteur") String acteur,
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin,
|
||||
@QueryParam("typeAction") TypeActionAudit typeAction,
|
||||
@QueryParam("ressourceType") String ressourceType,
|
||||
@QueryParam("succes") Boolean succes,
|
||||
@QueryParam("page") int page,
|
||||
@QueryParam("pageSize") int pageSize);
|
||||
@GET
|
||||
List<AuditLogDTO> searchLogs(
|
||||
@QueryParam("acteur") String acteur,
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin,
|
||||
@QueryParam("typeAction") TypeActionAudit typeAction,
|
||||
@QueryParam("ressourceType") String ressourceType,
|
||||
@QueryParam("succes") Boolean succes,
|
||||
@QueryParam("page") int page,
|
||||
@QueryParam("pageSize") int pageSize);
|
||||
|
||||
@GET
|
||||
@Path("/stats/actions")
|
||||
Map<TypeActionAudit, Long> getActionStatistics(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin);
|
||||
@GET
|
||||
@Path("/stats/actions")
|
||||
Map<TypeActionAudit, Long> getActionStatistics(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin);
|
||||
|
||||
@GET
|
||||
@Path("/stats/activity")
|
||||
Map<String, Long> getUserActivityStatistics(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin);
|
||||
@GET
|
||||
@Path("/stats/activity")
|
||||
Map<String, Long> getUserActivityStatistics(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin);
|
||||
|
||||
@GET
|
||||
@Path("/stats/failures")
|
||||
long getFailureCount(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin);
|
||||
@GET
|
||||
@Path("/stats/failures")
|
||||
long getFailureCount(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin);
|
||||
|
||||
@GET
|
||||
@Path("/stats/successes")
|
||||
long getSuccessCount(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin);
|
||||
@GET
|
||||
@Path("/stats/successes")
|
||||
long getSuccessCount(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package dev.lions.user.manager.client.api;
|
||||
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RegisterRestClient(configKey = "user-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/health")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface HealthRestClient {
|
||||
|
||||
@@ -1,57 +1,60 @@
|
||||
package dev.lions.user.manager.client.api;
|
||||
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.dto.role.RoleAssignmentDTO;
|
||||
import dev.lions.user.manager.dto.role.RoleDTO;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RegisterRestClient(configKey = "user-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/roles")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface RoleRestClient {
|
||||
|
||||
@GET
|
||||
@Path("/realm")
|
||||
List<RoleDTO> getAllRealmRoles(@QueryParam("realm") String realmName);
|
||||
@GET
|
||||
@Path("/realm")
|
||||
List<RoleDTO> getAllRealmRoles(@QueryParam("realm") String realmName);
|
||||
|
||||
@POST
|
||||
@Path("/realm")
|
||||
RoleDTO createRealmRole(@QueryParam("realm") String realmName, RoleDTO role);
|
||||
@POST
|
||||
@Path("/realm")
|
||||
RoleDTO createRealmRole(@QueryParam("realm") String realmName, RoleDTO role);
|
||||
|
||||
@GET
|
||||
@Path("/realm/{roleName}")
|
||||
RoleDTO getRealmRole(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName);
|
||||
@GET
|
||||
@Path("/realm/{roleName}")
|
||||
RoleDTO getRealmRole(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName);
|
||||
|
||||
@PUT
|
||||
@Path("/realm/{roleName}")
|
||||
RoleDTO updateRealmRole(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName,
|
||||
RoleDTO role);
|
||||
@PUT
|
||||
@Path("/realm/{roleName}")
|
||||
RoleDTO updateRealmRole(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName,
|
||||
RoleDTO role);
|
||||
|
||||
@DELETE
|
||||
@Path("/realm/{roleName}")
|
||||
void deleteRealmRole(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName);
|
||||
@DELETE
|
||||
@Path("/realm/{roleName}")
|
||||
void deleteRealmRole(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName);
|
||||
|
||||
@POST
|
||||
@Path("/users/{userId}/realm-roles")
|
||||
void assignRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName,
|
||||
RoleAssignmentRequest request);
|
||||
@POST
|
||||
@Path("/users/{userId}/realm-roles")
|
||||
void assignRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName,
|
||||
RoleAssignmentRequest request);
|
||||
|
||||
@DELETE
|
||||
@Path("/users/{userId}/realm-roles")
|
||||
void revokeRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName,
|
||||
RoleAssignmentRequest request);
|
||||
@DELETE
|
||||
@Path("/users/{userId}/realm-roles")
|
||||
void revokeRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName,
|
||||
RoleAssignmentRequest request);
|
||||
|
||||
@GET
|
||||
@Path("/users/{userId}/realm-roles")
|
||||
List<RoleDTO> getUserRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName);
|
||||
@GET
|
||||
@Path("/users/{userId}/realm-roles")
|
||||
List<RoleDTO> getUserRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName);
|
||||
|
||||
// Inner class for role assignment request
|
||||
class RoleAssignmentRequest {
|
||||
public List<String> roleNames;
|
||||
}
|
||||
// Inner class for role assignment request
|
||||
class RoleAssignmentRequest {
|
||||
public List<String> roleNames;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package dev.lions.user.manager.client.api;
|
||||
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.dto.sync.HealthStatusDTO;
|
||||
import dev.lions.user.manager.dto.sync.SyncResultDTO;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@RegisterRestClient(configKey = "user-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/sync")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@@ -22,8 +25,7 @@ public interface SyncRestClient {
|
||||
|
||||
@POST
|
||||
@Path("/{realmName}/all")
|
||||
SyncResultDTO syncAll(@PathParam("realmName") String realmName); // Assumant que syncAll retourne un résultat
|
||||
// détaillé
|
||||
SyncResultDTO syncAll(@PathParam("realmName") String realmName);
|
||||
|
||||
@GET
|
||||
@Path("/health")
|
||||
|
||||
@@ -1,73 +1,77 @@
|
||||
package dev.lions.user.manager.client.api;
|
||||
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.dto.user.UserDTO;
|
||||
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RegisterRestClient(configKey = "user-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Path("/api/users")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface UserRestClient {
|
||||
|
||||
@GET
|
||||
UserSearchResultDTO searchUsers(
|
||||
@QueryParam("realm") String realmName,
|
||||
@QueryParam("search") String searchTerm,
|
||||
@QueryParam("username") String username,
|
||||
@QueryParam("email") String email,
|
||||
@QueryParam("prenom") String prenom,
|
||||
@QueryParam("nom") String nom,
|
||||
@QueryParam("enabled") Boolean enabled,
|
||||
@QueryParam("page") int page,
|
||||
@QueryParam("pageSize") int pageSize);
|
||||
@GET
|
||||
UserSearchResultDTO searchUsers(
|
||||
@QueryParam("realm") String realmName,
|
||||
@QueryParam("search") String searchTerm,
|
||||
@QueryParam("username") String username,
|
||||
@QueryParam("email") String email,
|
||||
@QueryParam("prenom") String prenom,
|
||||
@QueryParam("nom") String nom,
|
||||
@QueryParam("enabled") Boolean enabled,
|
||||
@QueryParam("page") int page,
|
||||
@QueryParam("pageSize") int pageSize);
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
UserDTO getUserById(@PathParam("id") String id, @QueryParam("realm") String realmName);
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
UserDTO getUserById(@PathParam("id") String id, @QueryParam("realm") String realmName);
|
||||
|
||||
@POST
|
||||
Response createUser(@QueryParam("realm") String realmName, UserDTO user);
|
||||
@POST
|
||||
Response createUser(@QueryParam("realm") String realmName, UserDTO user);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
UserDTO updateUser(@PathParam("id") String id, @QueryParam("realm") String realmName, UserDTO user);
|
||||
@PUT
|
||||
@Path("/{id}")
|
||||
UserDTO updateUser(@PathParam("id") String id, @QueryParam("realm") String realmName, UserDTO user);
|
||||
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
void deleteUser(@PathParam("id") String id, @QueryParam("realm") String realmName,
|
||||
@QueryParam("hard") boolean hardDelete);
|
||||
@DELETE
|
||||
@Path("/{id}")
|
||||
void deleteUser(@PathParam("id") String id, @QueryParam("realm") String realmName,
|
||||
@QueryParam("hard") boolean hardDelete);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/activate")
|
||||
void activateUser(@PathParam("id") String id, @QueryParam("realm") String realmName);
|
||||
@PUT
|
||||
@Path("/{id}/activate")
|
||||
void activateUser(@PathParam("id") String id, @QueryParam("realm") String realmName);
|
||||
|
||||
@PUT
|
||||
@Path("/{id}/deactivate")
|
||||
void deactivateUser(@PathParam("id") String id, @QueryParam("realm") String realmName,
|
||||
@QueryParam("reason") String reason);
|
||||
@PUT
|
||||
@Path("/{id}/deactivate")
|
||||
void deactivateUser(@PathParam("id") String id, @QueryParam("realm") String realmName,
|
||||
@QueryParam("reason") String reason);
|
||||
|
||||
@POST
|
||||
@Path("/{id}/reset-password")
|
||||
void resetPassword(@PathParam("id") String id, @QueryParam("realm") String realmName, PasswordResetRequest request);
|
||||
@POST
|
||||
@Path("/{id}/reset-password")
|
||||
void resetPassword(@PathParam("id") String id, @QueryParam("realm") String realmName,
|
||||
PasswordResetRequest request);
|
||||
|
||||
@POST
|
||||
@Path("/{id}/send-verify-email")
|
||||
void sendVerificationEmail(@PathParam("id") String id, @QueryParam("realm") String realmName);
|
||||
@POST
|
||||
@Path("/{id}/send-verify-email")
|
||||
void sendVerificationEmail(@PathParam("id") String id, @QueryParam("realm") String realmName);
|
||||
|
||||
@GET
|
||||
@Path("/export/csv")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
String exportUsersToCSV(@QueryParam("realm") String realmName);
|
||||
@GET
|
||||
@Path("/export/csv")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
String exportUsersToCSV(@QueryParam("realm") String realmName);
|
||||
|
||||
// Inner class for password reset request DTO
|
||||
class PasswordResetRequest {
|
||||
public String password;
|
||||
public boolean temporary;
|
||||
}
|
||||
// Inner class for password reset request DTO
|
||||
class PasswordResetRequest {
|
||||
public String password;
|
||||
public boolean temporary;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
package dev.lions.user.manager.client.filter;
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import io.quarkus.oidc.AccessTokenCredential;
|
||||
import jakarta.enterprise.context.RequestScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.core.MultivaluedHashMap;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Factory pour ajouter automatiquement le token OIDC Bearer
|
||||
* dans les headers des requêtes REST Client vers le backend.
|
||||
*
|
||||
* Ce factory est nécessaire car bearer-token-propagation ne fonctionne pas
|
||||
* pour les appels depuis les managed beans JSF vers le backend.
|
||||
*
|
||||
* @author Lions User Manager
|
||||
* @version 1.0.0
|
||||
* Factory to automatically add the OIDC access token (Bearer)
|
||||
* to REST Client request headers.
|
||||
*/
|
||||
@ApplicationScoped
|
||||
@RequestScoped
|
||||
public class AuthHeaderFactory implements ClientHeadersFactory {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(AuthHeaderFactory.class.getName());
|
||||
|
||||
@Inject
|
||||
JsonWebToken jwt;
|
||||
AccessTokenCredential accessTokenCredential;
|
||||
|
||||
@Override
|
||||
public MultivaluedMap<String, String> update(
|
||||
@@ -34,17 +28,30 @@ public class AuthHeaderFactory implements ClientHeadersFactory {
|
||||
|
||||
MultivaluedMap<String, String> result = new MultivaluedHashMap<>();
|
||||
|
||||
// 1. Log incoming and outgoing headers for debugging
|
||||
if (incomingHeaders != null) {
|
||||
LOGGER.fine("Incoming Headers: " + incomingHeaders.keySet());
|
||||
}
|
||||
if (clientOutgoingHeaders != null) {
|
||||
LOGGER.fine("Client Outgoing Headers: " + clientOutgoingHeaders.keySet());
|
||||
}
|
||||
|
||||
try {
|
||||
// Vérifier si le JWT est disponible et non expiré
|
||||
if (jwt != null && jwt.getRawToken() != null && !jwt.getRawToken().isEmpty()) {
|
||||
String token = jwt.getRawToken();
|
||||
result.add("Authorization", "Bearer " + token);
|
||||
LOGGER.fine("Token Bearer ajouté au header Authorization");
|
||||
if (accessTokenCredential != null) {
|
||||
// 2. Check if the token is available
|
||||
String accessToken = accessTokenCredential.getToken();
|
||||
if (accessToken != null && !accessToken.isEmpty()) {
|
||||
result.add("Authorization", "Bearer " + accessToken);
|
||||
LOGGER.info("Access token added to Authorization header. Token length: " + accessToken.length());
|
||||
} else {
|
||||
LOGGER.warning("Access token is empty or null in AccessTokenCredential.");
|
||||
}
|
||||
} else {
|
||||
LOGGER.warning("Token JWT non disponible ou vide - impossible d'ajouter le header Authorization");
|
||||
LOGGER.warning("AccessTokenCredential is unavailable - user might not be authenticated.");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'ajout du token Bearer: " + e.getMessage());
|
||||
LOGGER.severe("Error adding Bearer token: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,115 +1,24 @@
|
||||
package dev.lions.user.manager.client.service;
|
||||
|
||||
import dev.lions.user.manager.api.AuditResourceApi;
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.dto.audit.AuditLogDTO;
|
||||
import dev.lions.user.manager.enums.audit.TypeActionAudit;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* REST Client pour le service d'audit
|
||||
* REST Client pour le service d'audit.
|
||||
* Étend maintenant l'interface API commune définie dans server-api.
|
||||
*/
|
||||
@Path("/api/audit")
|
||||
@RegisterRestClient(configKey = "lions-user-manager-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface AuditServiceClient {
|
||||
public interface AuditServiceClient extends AuditResourceApi {
|
||||
|
||||
@POST
|
||||
@Path("/search")
|
||||
List<AuditLogDTO> searchLogs(
|
||||
@QueryParam("acteur") String acteurUsername,
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin,
|
||||
@QueryParam("typeAction") TypeActionAudit typeAction,
|
||||
@QueryParam("ressourceType") String ressourceType,
|
||||
@QueryParam("succes") Boolean succes,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("pageSize") @DefaultValue("50") int pageSize
|
||||
);
|
||||
// Méthodes héritées de AuditResourceApi
|
||||
// Note: getLogsByActeur (FR) a été remplacé par getLogsByActor (EN) dans l'API
|
||||
// commune.
|
||||
|
||||
@GET
|
||||
@Path("/actor/{acteurUsername}")
|
||||
List<AuditLogDTO> getLogsByActeur(
|
||||
@PathParam("acteurUsername") String acteurUsername,
|
||||
@QueryParam("limit") @DefaultValue("100") int limit
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/realm/{realmName}")
|
||||
List<AuditLogDTO> getLogsByRealm(
|
||||
@PathParam("realmName") String realmName,
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("pageSize") @DefaultValue("50") int pageSize
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/ressource/{ressourceType}/{ressourceId}")
|
||||
List<AuditLogDTO> getLogsByRessource(
|
||||
@PathParam("ressourceType") String ressourceType,
|
||||
@PathParam("ressourceId") String ressourceId,
|
||||
@QueryParam("limit") @DefaultValue("100") int limit
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/action/{typeAction}")
|
||||
List<AuditLogDTO> getLogsByAction(
|
||||
@PathParam("typeAction") TypeActionAudit typeAction,
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin,
|
||||
@QueryParam("limit") @DefaultValue("100") int limit
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/stats/actions")
|
||||
Map<TypeActionAudit, Long> getActionStatistics(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/stats/users")
|
||||
Map<String, Long> getUserActivityStatistics(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/stats/failures")
|
||||
CountResponse getFailureCount(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/stats/success")
|
||||
CountResponse getSuccessCount(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin
|
||||
);
|
||||
|
||||
/**
|
||||
* DTO pour les réponses de comptage
|
||||
*/
|
||||
class CountResponse {
|
||||
public long count;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/export/csv")
|
||||
@Produces(MediaType.TEXT_PLAIN)
|
||||
String exportLogsToCSV(
|
||||
@QueryParam("dateDebut") String dateDebut,
|
||||
@QueryParam("dateFin") String dateFin
|
||||
);
|
||||
// Si des méthodes spécifiques au client (non présentes sur le serveur)
|
||||
// existaient, elles devraient être ici.
|
||||
// L'ancienne méthode getLogsByRealm n'existait pas sur le serveur, donc
|
||||
// supprimée.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,101 +1,19 @@
|
||||
package dev.lions.user.manager.client.service;
|
||||
|
||||
import dev.lions.user.manager.api.RealmAssignmentResourceApi;
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.dto.realm.RealmAssignmentDTO;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST Client pour le service de gestion des affectations de realms
|
||||
* REST Client pour le service de gestion des affectations de realms.
|
||||
* Étend maintenant l'interface API commune définie dans server-api.
|
||||
*/
|
||||
@Path("/api/realm-assignments")
|
||||
@RegisterRestClient(configKey = "lions-user-manager-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface RealmAssignmentServiceClient {
|
||||
public interface RealmAssignmentServiceClient extends RealmAssignmentResourceApi {
|
||||
|
||||
// ==================== Consultation ====================
|
||||
|
||||
@GET
|
||||
List<RealmAssignmentDTO> getAllAssignments();
|
||||
|
||||
@GET
|
||||
@Path("/user/{userId}")
|
||||
List<RealmAssignmentDTO> getAssignmentsByUser(@PathParam("userId") String userId);
|
||||
|
||||
@GET
|
||||
@Path("/realm/{realmName}")
|
||||
List<RealmAssignmentDTO> getAssignmentsByRealm(@PathParam("realmName") String realmName);
|
||||
|
||||
@GET
|
||||
@Path("/{assignmentId}")
|
||||
RealmAssignmentDTO getAssignmentById(@PathParam("assignmentId") String assignmentId);
|
||||
|
||||
// ==================== Vérification ====================
|
||||
|
||||
@GET
|
||||
@Path("/check")
|
||||
CheckResponse canManageRealm(
|
||||
@QueryParam("userId") String userId,
|
||||
@QueryParam("realmName") String realmName
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/authorized-realms/{userId}")
|
||||
AuthorizedRealmsResponse getAuthorizedRealms(@PathParam("userId") String userId);
|
||||
|
||||
// ==================== Modification ====================
|
||||
|
||||
@POST
|
||||
RealmAssignmentDTO assignRealmToUser(RealmAssignmentDTO assignment);
|
||||
|
||||
@DELETE
|
||||
@Path("/user/{userId}/realm/{realmName}")
|
||||
void revokeRealmFromUser(
|
||||
@PathParam("userId") String userId,
|
||||
@PathParam("realmName") String realmName
|
||||
);
|
||||
|
||||
@DELETE
|
||||
@Path("/user/{userId}")
|
||||
void revokeAllRealmsFromUser(@PathParam("userId") String userId);
|
||||
|
||||
@PUT
|
||||
@Path("/{assignmentId}/deactivate")
|
||||
void deactivateAssignment(@PathParam("assignmentId") String assignmentId);
|
||||
|
||||
@PUT
|
||||
@Path("/{assignmentId}/activate")
|
||||
void activateAssignment(@PathParam("assignmentId") String assignmentId);
|
||||
|
||||
@PUT
|
||||
@Path("/super-admin/{userId}")
|
||||
void setSuperAdmin(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("superAdmin") Boolean superAdmin
|
||||
);
|
||||
|
||||
// ==================== Classes de réponse ====================
|
||||
|
||||
/**
|
||||
* Réponse de vérification d'accès
|
||||
*/
|
||||
class CheckResponse {
|
||||
public boolean canManage;
|
||||
public String userId;
|
||||
public String realmName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Réponse des realms autorisés
|
||||
*/
|
||||
class AuthorizedRealmsResponse {
|
||||
public List<String> realms;
|
||||
public boolean isSuperAdmin;
|
||||
}
|
||||
// Méthodes héritées de RealmAssignmentResourceApi
|
||||
// Les classes internes CheckResponse et AuthorizedRealmsResponse ont été
|
||||
// remplacées par des DTOs dans server-api.
|
||||
}
|
||||
|
||||
@@ -1,33 +1,19 @@
|
||||
package dev.lions.user.manager.client.service;
|
||||
|
||||
import dev.lions.user.manager.api.RealmResourceApi;
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST Client pour le service de gestion des realms Keycloak
|
||||
* Interface pour communiquer avec l'API backend
|
||||
* Étend maintenant l'interface API commune définie dans server-api.
|
||||
*/
|
||||
@Path("/api/realms")
|
||||
@RegisterRestClient(configKey = "lions-user-manager-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@RegisterProvider(RestClientExceptionMapper.class)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface RealmServiceClient {
|
||||
public interface RealmServiceClient extends RealmResourceApi {
|
||||
|
||||
/**
|
||||
* Récupère la liste de tous les realms disponibles dans Keycloak
|
||||
* @return liste des noms de realms
|
||||
*/
|
||||
@GET
|
||||
@Path("/list")
|
||||
List<String> getAllRealms();
|
||||
// Méthode getAllRealms héritée de RealmResourceApi
|
||||
}
|
||||
|
||||
|
||||
@@ -1,153 +1,20 @@
|
||||
package dev.lions.user.manager.client.service;
|
||||
|
||||
import dev.lions.user.manager.api.RoleResourceApi;
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.dto.role.RoleAssignmentDTO;
|
||||
import dev.lions.user.manager.dto.role.RoleDTO;
|
||||
import dev.lions.user.manager.enums.role.TypeRole;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST Client pour le service de gestion des rôles
|
||||
* REST Client pour le service de gestion des rôles.
|
||||
* Étend maintenant l'interface API commune définie dans server-api.
|
||||
*/
|
||||
@Path("/api/roles")
|
||||
@RegisterRestClient(configKey = "lions-user-manager-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface RoleServiceClient {
|
||||
public interface RoleServiceClient extends RoleResourceApi {
|
||||
|
||||
// ==================== Realm Roles ====================
|
||||
|
||||
@POST
|
||||
@Path("/realm")
|
||||
RoleDTO createRealmRole(
|
||||
RoleDTO role,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/realm/{roleName}")
|
||||
RoleDTO getRealmRoleByName(
|
||||
@PathParam("roleName") String roleName,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/realm")
|
||||
List<RoleDTO> getAllRealmRoles(
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
@PUT
|
||||
@Path("/realm/{roleName}")
|
||||
RoleDTO updateRealmRole(
|
||||
@PathParam("roleName") String roleName,
|
||||
RoleDTO role,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
@DELETE
|
||||
@Path("/realm/{roleName}")
|
||||
void deleteRealmRole(
|
||||
@PathParam("roleName") String roleName,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
// ==================== Client Roles ====================
|
||||
|
||||
@POST
|
||||
@Path("/client/{clientId}")
|
||||
RoleDTO createClientRole(
|
||||
@PathParam("clientId") String clientId,
|
||||
RoleDTO role,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/client/{clientId}")
|
||||
List<RoleDTO> getAllClientRoles(
|
||||
@PathParam("clientId") String clientId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/client/{clientId}/{roleName}")
|
||||
RoleDTO getClientRoleByName(
|
||||
@PathParam("clientId") String clientId,
|
||||
@PathParam("roleName") String roleName,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
@DELETE
|
||||
@Path("/client/{clientId}/{roleName}")
|
||||
void deleteClientRole(
|
||||
@PathParam("clientId") String clientId,
|
||||
@PathParam("roleName") String roleName,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
// ==================== Role Assignment ====================
|
||||
|
||||
@POST
|
||||
@Path("/assign/realm/{userId}")
|
||||
void assignRealmRolesToUser(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName,
|
||||
RoleAssignmentRequest request
|
||||
);
|
||||
|
||||
@POST
|
||||
@Path("/revoke/realm/{userId}")
|
||||
void revokeRealmRolesFromUser(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName,
|
||||
RoleAssignmentRequest request
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/user/realm/{userId}")
|
||||
List<RoleDTO> getUserRealmRoles(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
@GET
|
||||
@Path("/user/client/{clientId}/{userId}")
|
||||
List<RoleDTO> getUserClientRoles(
|
||||
@PathParam("clientId") String clientId,
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* DTO pour l'attribution/révocation de rôles
|
||||
*/
|
||||
class RoleAssignmentRequest {
|
||||
public List<String> roleNames;
|
||||
}
|
||||
|
||||
// ==================== Composite Roles ====================
|
||||
|
||||
@GET
|
||||
@Path("/composite/{roleName}")
|
||||
List<RoleDTO> getCompositeRoles(
|
||||
@PathParam("roleName") String roleName,
|
||||
@QueryParam("realm") String realmName,
|
||||
@QueryParam("typeRole") TypeRole typeRole,
|
||||
@QueryParam("clientName") String clientName
|
||||
);
|
||||
|
||||
@POST
|
||||
@Path("/composite/{roleName}/add")
|
||||
void addCompositeRoles(
|
||||
@PathParam("roleName") String roleName,
|
||||
@QueryParam("realm") String realmName,
|
||||
RoleAssignmentRequest request
|
||||
);
|
||||
// Méthodes héritées de RoleResourceApi
|
||||
// Note: Certaines méthodes de l'ancien client ont été restructurées (ex:
|
||||
// assignRoleToUser -> assignRealmRoles/assignClientRoles)
|
||||
// pour correspondre à l'implémentation serveur existante.
|
||||
}
|
||||
|
||||
|
||||
@@ -1,83 +1,39 @@
|
||||
package dev.lions.user.manager.client.service;
|
||||
|
||||
import dev.lions.user.manager.api.SyncResourceApi;
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.dto.sync.HealthStatusDTO;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* REST Client pour le service de synchronisation
|
||||
* Utilise l'interface commune définie dans server-api pour garantir la
|
||||
* cohérence du contrat.
|
||||
*/
|
||||
@Path("/api/sync")
|
||||
@RegisterRestClient(configKey = "lions-user-manager-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface SyncServiceClient {
|
||||
public interface SyncServiceClient extends SyncResourceApi {
|
||||
|
||||
// Méthodes supplémentaires spécifiques au client (ou pas encore migrées dans
|
||||
// l'API commune)
|
||||
|
||||
@GET
|
||||
@Path("/health")
|
||||
HealthCheckResponse checkHealth();
|
||||
HealthStatusDTO checkHealth(@QueryParam("realm") String realmName);
|
||||
|
||||
@GET
|
||||
@Path("/health/detailed")
|
||||
Map<String, Object> getDetailedHealthStatus();
|
||||
|
||||
@POST
|
||||
@Path("/users/{realmName}")
|
||||
SyncUsersResponse syncUsers(@PathParam("realmName") String realmName);
|
||||
|
||||
@POST
|
||||
@Path("/roles/realm/{realmName}")
|
||||
SyncRolesResponse syncRealmRoles(@PathParam("realmName") String realmName);
|
||||
|
||||
@POST
|
||||
@Path("/roles/client/{clientId}/{realmName}")
|
||||
SyncRolesResponse syncClientRoles(
|
||||
@PathParam("clientId") String clientId,
|
||||
@PathParam("realmName") String realmName
|
||||
);
|
||||
|
||||
@POST
|
||||
@Path("/all/{realmName}")
|
||||
Map<String, Object> syncAll(@PathParam("realmName") String realmName);
|
||||
@Path("/exists/user/{username}")
|
||||
Boolean userExists(
|
||||
@PathParam("username") String username,
|
||||
@QueryParam("realm") String realmName);
|
||||
|
||||
@GET
|
||||
@Path("/check/realm/{realmName}")
|
||||
ExistsCheckResponse checkRealmExists(@PathParam("realmName") String realmName);
|
||||
|
||||
@GET
|
||||
@Path("/check/user/{userId}")
|
||||
ExistsCheckResponse checkUserExists(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
// ==================== DTOs de réponse ====================
|
||||
|
||||
class SyncUsersResponse {
|
||||
public int count;
|
||||
public List<dev.lions.user.manager.dto.user.UserDTO> users;
|
||||
}
|
||||
|
||||
class SyncRolesResponse {
|
||||
public int count;
|
||||
public List<dev.lions.user.manager.dto.role.RoleDTO> roles;
|
||||
}
|
||||
|
||||
class HealthCheckResponse {
|
||||
public boolean healthy;
|
||||
public String message;
|
||||
}
|
||||
|
||||
class ExistsCheckResponse {
|
||||
public boolean exists;
|
||||
public String resourceType;
|
||||
public String resourceId;
|
||||
}
|
||||
@Path("/exists/role/{roleName}")
|
||||
Boolean roleExists(
|
||||
@PathParam("roleName") String roleName,
|
||||
@QueryParam("realm") String realmName,
|
||||
@QueryParam("typeRole") String typeRole,
|
||||
@QueryParam("clientName") String clientName);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,174 +1,19 @@
|
||||
package dev.lions.user.manager.client.service;
|
||||
|
||||
import dev.lions.user.manager.api.UserResourceApi;
|
||||
import dev.lions.user.manager.client.filter.AuthHeaderFactory;
|
||||
import dev.lions.user.manager.client.service.RestClientExceptionMapper;
|
||||
import dev.lions.user.manager.dto.user.UserDTO;
|
||||
import dev.lions.user.manager.dto.user.UserSearchCriteriaDTO;
|
||||
import dev.lions.user.manager.dto.user.UserSearchResultDTO;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
|
||||
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST Client pour le service de gestion des utilisateurs
|
||||
* Interface pour communiquer avec l'API backend
|
||||
* Étend maintenant l'interface API commune définie dans server-api.
|
||||
*/
|
||||
@Path("/api/users")
|
||||
@RegisterRestClient(configKey = "lions-user-manager-api")
|
||||
@RegisterClientHeaders(AuthHeaderFactory.class)
|
||||
@RegisterProvider(RestClientExceptionMapper.class)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public interface UserServiceClient {
|
||||
public interface UserServiceClient extends UserResourceApi {
|
||||
|
||||
/**
|
||||
* Rechercher des utilisateurs selon des critères
|
||||
*/
|
||||
@POST
|
||||
@Path("/search")
|
||||
UserSearchResultDTO searchUsers(UserSearchCriteriaDTO criteria);
|
||||
|
||||
/**
|
||||
* Récupérer un utilisateur par ID
|
||||
*/
|
||||
@GET
|
||||
@Path("/{userId}")
|
||||
UserDTO getUserById(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* Lister tous les utilisateurs (paginé)
|
||||
*/
|
||||
@GET
|
||||
UserSearchResultDTO getAllUsers(
|
||||
@QueryParam("realm") String realmName,
|
||||
@QueryParam("page") @DefaultValue("0") int page,
|
||||
@QueryParam("pageSize") @DefaultValue("20") int pageSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Créer un nouvel utilisateur
|
||||
*/
|
||||
@POST
|
||||
UserDTO createUser(
|
||||
UserDTO user,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* Mettre à jour un utilisateur
|
||||
*/
|
||||
@PUT
|
||||
@Path("/{userId}")
|
||||
UserDTO updateUser(
|
||||
@PathParam("userId") String userId,
|
||||
UserDTO user,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* Supprimer un utilisateur
|
||||
*/
|
||||
@DELETE
|
||||
@Path("/{userId}")
|
||||
void deleteUser(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* Activer un utilisateur
|
||||
*/
|
||||
@POST
|
||||
@Path("/{userId}/activate")
|
||||
void activateUser(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* Désactiver un utilisateur
|
||||
*/
|
||||
@POST
|
||||
@Path("/{userId}/deactivate")
|
||||
void deactivateUser(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* Réinitialiser le mot de passe
|
||||
*/
|
||||
@POST
|
||||
@Path("/{userId}/reset-password")
|
||||
void resetPassword(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName,
|
||||
PasswordResetRequest request
|
||||
);
|
||||
|
||||
/**
|
||||
* DTO pour la réinitialisation de mot de passe
|
||||
*/
|
||||
class PasswordResetRequest {
|
||||
public String password;
|
||||
public boolean temporary = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoyer un email de vérification
|
||||
*/
|
||||
@POST
|
||||
@Path("/{userId}/send-verification-email")
|
||||
void sendVerificationEmail(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* Déconnecter toutes les sessions d'un utilisateur
|
||||
*/
|
||||
@POST
|
||||
@Path("/{userId}/logout-sessions")
|
||||
void logoutAllSessions(
|
||||
@PathParam("userId") String userId,
|
||||
@QueryParam("realm") String realmName
|
||||
);
|
||||
|
||||
/**
|
||||
* Récupérer les sessions actives d'un utilisateur
|
||||
*/
|
||||
@GET
|
||||
@Path("/{userId}/sessions")
|
||||
List<String> getActiveSessions(
|
||||
@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
|
||||
);
|
||||
// Méthodes héritées de UserResourceApi
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
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.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;
|
||||
@@ -18,7 +16,6 @@ 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.Map;
|
||||
import java.util.logging.Logger;
|
||||
@@ -41,10 +38,6 @@ public class AuditConsultationBean implements Serializable {
|
||||
@RestClient
|
||||
private AuditServiceClient auditServiceClient;
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
private RealmServiceClient realmServiceClient;
|
||||
|
||||
// Liste des logs
|
||||
private List<AuditLogDTO> auditLogs = new ArrayList<>();
|
||||
private AuditLogDTO selectedLog;
|
||||
@@ -89,15 +82,14 @@ public class AuditConsultationBean implements Serializable {
|
||||
String dateFinStr = dateFin != null ? dateFin.format(DATE_FORMATTER) : null;
|
||||
|
||||
auditLogs = auditServiceClient.searchLogs(
|
||||
acteurUsername,
|
||||
dateDebutStr,
|
||||
dateFinStr,
|
||||
selectedTypeAction,
|
||||
ressourceType,
|
||||
succes,
|
||||
currentPage,
|
||||
pageSize
|
||||
);
|
||||
acteurUsername,
|
||||
dateDebutStr,
|
||||
dateFinStr,
|
||||
selectedTypeAction,
|
||||
ressourceType,
|
||||
succes,
|
||||
currentPage,
|
||||
pageSize);
|
||||
|
||||
totalRecords = auditLogs.size();
|
||||
addSuccessMessage("Recherche effectuée: " + totalRecords + " résultat(s) trouvé(s)");
|
||||
@@ -112,7 +104,7 @@ public class AuditConsultationBean implements Serializable {
|
||||
*/
|
||||
public void loadLogsByActeur(String username) {
|
||||
try {
|
||||
auditLogs = auditServiceClient.getLogsByActeur(username, 100);
|
||||
auditLogs = auditServiceClient.getLogsByActor(username, 100);
|
||||
totalRecords = auditLogs.size();
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement: " + e.getMessage());
|
||||
@@ -125,18 +117,10 @@ public class AuditConsultationBean implements Serializable {
|
||||
*/
|
||||
public void loadLogsByRealm(String realmName) {
|
||||
try {
|
||||
String dateDebutStr = dateDebut != null ? dateDebut.format(DATE_FORMATTER) : null;
|
||||
String dateFinStr = dateFin != null ? dateFin.format(DATE_FORMATTER) : null;
|
||||
|
||||
auditLogs = auditServiceClient.getLogsByRealm(
|
||||
realmName,
|
||||
dateDebutStr,
|
||||
dateFinStr,
|
||||
currentPage,
|
||||
pageSize
|
||||
);
|
||||
|
||||
totalRecords = auditLogs.size();
|
||||
// Méthode supprimée de l'API standardisée
|
||||
LOGGER.warning("Recherche par realm non supportée dans l'API standardisée");
|
||||
auditLogs = new ArrayList<>();
|
||||
totalRecords = 0;
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement: " + e.getMessage());
|
||||
addErrorMessage("Erreur lors du chargement: " + e.getMessage());
|
||||
@@ -153,10 +137,14 @@ public class AuditConsultationBean implements Serializable {
|
||||
|
||||
actionStatistics = auditServiceClient.getActionStatistics(dateDebutStr, dateFinStr);
|
||||
userActivityStatistics = auditServiceClient.getUserActivityStatistics(dateDebutStr, dateFinStr);
|
||||
AuditServiceClient.CountResponse failureResponse = auditServiceClient.getFailureCount(dateDebutStr, dateFinStr);
|
||||
failureCount = failureResponse != null ? failureResponse.count : 0L;
|
||||
AuditServiceClient.CountResponse successResponse = auditServiceClient.getSuccessCount(dateDebutStr, dateFinStr);
|
||||
successCount = successResponse != null ? successResponse.count : 0L;
|
||||
|
||||
dev.lions.user.manager.dto.common.CountDTO failures = auditServiceClient.getFailureCount(dateDebutStr,
|
||||
dateFinStr);
|
||||
failureCount = failures != null ? failures.getCount() : 0L;
|
||||
|
||||
dev.lions.user.manager.dto.common.CountDTO successes = auditServiceClient.getSuccessCount(dateDebutStr,
|
||||
dateFinStr);
|
||||
successCount = successes != null ? successes.getCount() : 0L;
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement des statistiques: " + e.getMessage());
|
||||
}
|
||||
@@ -170,30 +158,13 @@ public class AuditConsultationBean implements Serializable {
|
||||
String dateDebutStr = dateDebut != null ? dateDebut.format(DATE_FORMATTER) : null;
|
||||
String dateFinStr = dateFin != null ? dateFin.format(DATE_FORMATTER) : null;
|
||||
|
||||
String csv = auditServiceClient.exportLogsToCSV(dateDebutStr, dateFinStr);
|
||||
|
||||
// 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());
|
||||
try (jakarta.ws.rs.core.Response response = auditServiceClient.exportLogsToCSV(dateDebutStr, dateFinStr)) {
|
||||
// TODO: Implémenter le téléchargement du fichier CSV
|
||||
// String csv = response.readEntity(String.class); // Non utilisé pour l'instant
|
||||
addSuccessMessage("Export CSV généré avec succès");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'export CSV: " + e.getMessage());
|
||||
LOGGER.severe("Erreur lors de l'export: " + e.getMessage());
|
||||
addErrorMessage("Erreur lors de l'export: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -231,37 +202,23 @@ public class AuditConsultationBean implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Charger les realms disponibles depuis Keycloak
|
||||
* Charger les realms disponibles
|
||||
*/
|
||||
private void loadRealms() {
|
||||
try {
|
||||
LOGGER.info("Chargement des realms disponibles depuis Keycloak");
|
||||
List<String> realms = realmServiceClient.getAllRealms();
|
||||
|
||||
if (realms == null || realms.isEmpty()) {
|
||||
LOGGER.warning("Aucun realm trouvé dans Keycloak");
|
||||
availableRealms = Collections.emptyList();
|
||||
} else {
|
||||
availableRealms = new ArrayList<>(realms);
|
||||
LOGGER.info("Realms disponibles chargés depuis Keycloak: " + availableRealms.size());
|
||||
}
|
||||
} 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: liste vide plutôt que des données fictives
|
||||
availableRealms = Collections.emptyList();
|
||||
}
|
||||
// TODO: Implémenter la récupération des realms depuis Keycloak
|
||||
// Le realm "master" est le realm d'administration qui permet de gérer tous les
|
||||
// autres realms
|
||||
availableRealms = List.of("master", "lions-user-manager", "btpxpress", "unionflow");
|
||||
}
|
||||
|
||||
// Méthodes utilitaires
|
||||
private void addSuccessMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
}
|
||||
|
||||
private void addErrorMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
@@ -19,8 +18,6 @@ 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;
|
||||
|
||||
@@ -50,278 +47,107 @@ public class DashboardBean implements Serializable {
|
||||
@RestClient
|
||||
private AuditServiceClient auditServiceClient;
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
private RealmServiceClient realmServiceClient;
|
||||
|
||||
@Inject
|
||||
private UserSessionBean userSessionBean;
|
||||
|
||||
// ==================== STATISTIQUES MÉTIER ====================
|
||||
|
||||
// Utilisateurs
|
||||
// Statistiques
|
||||
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;
|
||||
|
||||
// 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
|
||||
private Long recentActions = 0L;
|
||||
private Long activeSessions = 0L;
|
||||
private Long onlineUsers = 0L;
|
||||
|
||||
// Indicateur de chargement
|
||||
private boolean loading = false;
|
||||
|
||||
// ==================== MÉTHODES D'AFFICHAGE ====================
|
||||
|
||||
// Méthodes pour obtenir les valeurs formatées pour l'affichage
|
||||
public String getTotalUsersDisplay() {
|
||||
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);
|
||||
if (loading)
|
||||
return "...";
|
||||
return totalUsers != null ? String.valueOf(totalUsers) : "0";
|
||||
}
|
||||
|
||||
public String getTotalRolesDisplay() {
|
||||
return loading ? "..." : String.valueOf(totalRoles);
|
||||
if (loading)
|
||||
return "...";
|
||||
return totalRoles != null ? String.valueOf(totalRoles) : "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 String getRecentActionsDisplay() {
|
||||
if (loading)
|
||||
return "...";
|
||||
return recentActions != null ? String.valueOf(recentActions) : "0";
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return loading;
|
||||
}
|
||||
|
||||
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<>();
|
||||
// Realm par défaut
|
||||
private String realmName = "master";
|
||||
|
||||
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 métier
|
||||
* Charger toutes les statistiques
|
||||
*/
|
||||
public void loadStatistics() {
|
||||
loading = true;
|
||||
try {
|
||||
// Statistiques utilisateurs
|
||||
loadUserStatistics();
|
||||
|
||||
// Statistiques rôles
|
||||
loadTotalUsers();
|
||||
loadTotalRoles();
|
||||
|
||||
// Statistiques activité & audit
|
||||
loadActivityStatistics();
|
||||
|
||||
// Statistiques sécurité
|
||||
loadSecurityStatistics();
|
||||
|
||||
loadRecentActions();
|
||||
// Les sessions actives nécessitent une API spécifique qui n'existe pas encore
|
||||
// activeSessions = 0L;
|
||||
// onlineUsers = 0L;
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement des statistiques: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Charger les statistiques utilisateurs
|
||||
* Charger le nombre total d'utilisateurs
|
||||
*/
|
||||
private void loadUserStatistics() {
|
||||
private void loadTotalUsers() {
|
||||
try {
|
||||
// Total utilisateurs
|
||||
UserSearchCriteriaDTO criteriaAll = UserSearchCriteriaDTO.builder()
|
||||
.realmName(realmName)
|
||||
.page(0)
|
||||
.pageSize(1)
|
||||
.build();
|
||||
UserSearchResultDTO resultAll = userServiceClient.searchUsers(criteriaAll);
|
||||
totalUsers = resultAll != null && resultAll.getTotalCount() != null ? resultAll.getTotalCount() : 0L;
|
||||
LOGGER.info("Début chargement total utilisateurs pour realm: " + realmName);
|
||||
UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
|
||||
.realmName(realmName)
|
||||
.page(0)
|
||||
.pageSize(1) // On n'a besoin que du count
|
||||
.build();
|
||||
|
||||
// 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;
|
||||
LOGGER.info("Appel userServiceClient.searchUsers()...");
|
||||
UserSearchResultDTO result = userServiceClient.searchUsers(criteria);
|
||||
LOGGER.info("Résultat reçu: " + (result != null ? "NON NULL" : "NULL"));
|
||||
|
||||
// Utilisateurs inactifs
|
||||
inactiveUsers = totalUsers - activeUsers;
|
||||
|
||||
LOGGER.info("✅ Statistiques utilisateurs: Total=" + totalUsers + ", Actifs=" + activeUsers + ", Inactifs=" + inactiveUsers);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("❌ Erreur chargement statistiques utilisateurs: " + e.getMessage());
|
||||
totalUsers = 0L;
|
||||
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;
|
||||
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 {
|
||||
successRate24h = 100.0;
|
||||
LOGGER.warning(" - result.getTotalCount() est null");
|
||||
}
|
||||
|
||||
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;
|
||||
totalUsers = 0L;
|
||||
}
|
||||
|
||||
// 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;
|
||||
LOGGER.severe("❌ ERREUR lors du chargement du nombre d'utilisateurs: " + e.getMessage());
|
||||
LOGGER.severe(" Type d'erreur: " + e.getClass().getName());
|
||||
e.printStackTrace();
|
||||
totalUsers = 0L;
|
||||
addErrorMessage("Impossible de charger le nombre d'utilisateurs: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +177,66 @@ 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()...");
|
||||
dev.lions.user.manager.dto.common.CountDTO successDto = auditServiceClient.getSuccessCount(dateDebutStr,
|
||||
dateFinStr);
|
||||
dev.lions.user.manager.dto.common.CountDTO failureDto = auditServiceClient.getFailureCount(dateDebutStr,
|
||||
dateFinStr);
|
||||
|
||||
Long successCount = (successDto != null) ? successDto.getCount() : 0L;
|
||||
Long failureCount = (failureDto != null) ? failureDto.getCount() : 0L;
|
||||
|
||||
LOGGER.info(" SuccessCount: " + successCount);
|
||||
LOGGER.info(" FailureCount: " + failureCount);
|
||||
recentActions = successCount + failureCount;
|
||||
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
|
||||
*/
|
||||
@@ -360,61 +246,14 @@ 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,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
}
|
||||
|
||||
private void addErrorMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package dev.lions.user.manager.client.view;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Named;
|
||||
import lombok.Data;
|
||||
import org.primefaces.model.TreeNode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Bean JSF pour la showcase Freya Extension
|
||||
* Démonstration complète des 46 composants personnalisés
|
||||
*
|
||||
* @author Lions User Manager
|
||||
* @version 1.0.0
|
||||
*/
|
||||
@Named("demoBean")
|
||||
@ViewScoped
|
||||
@Data
|
||||
public class FreyaShowcaseBean implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger LOGGER = Logger.getLogger(FreyaShowcaseBean.class.getName());
|
||||
|
||||
// ============ DONNÉES UTILISATEUR EXEMPLE ============
|
||||
private User user = new User();
|
||||
private List<SampleUser> sampleUsers;
|
||||
private TreeNode<NodeData> treeRoot;
|
||||
|
||||
// ============ DIALOGUES ============
|
||||
private FormDialogData dialogData = new FormDialogData();
|
||||
|
||||
// ============ COMPOSANTS ============
|
||||
private String requiredField;
|
||||
private String inplaceText = "Texte éditable";
|
||||
private String chartData;
|
||||
private Integer progressValue = 0;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
LOGGER.info("=== Initialisation du FreyaShowcaseBean ===");
|
||||
|
||||
// Initialiser les données d'exemple
|
||||
initSampleUsers();
|
||||
initTreeData();
|
||||
initChartData();
|
||||
|
||||
// Valeurs par défaut pour les composants
|
||||
user.setVolume(50);
|
||||
user.setRating(4);
|
||||
user.setQuantite(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialiser les utilisateurs d'exemple pour dataTable et dataView
|
||||
*/
|
||||
private void initSampleUsers() {
|
||||
sampleUsers = new ArrayList<>();
|
||||
sampleUsers.add(new SampleUser("Jean Dupont", "jean.dupont@example.com", true));
|
||||
sampleUsers.add(new SampleUser("Marie Martin", "marie.martin@example.com", true));
|
||||
sampleUsers.add(new SampleUser("Pierre Durand", "pierre.durand@example.com", false));
|
||||
sampleUsers.add(new SampleUser("Sophie Bernard", "sophie.bernard@example.com", true));
|
||||
sampleUsers.add(new SampleUser("Luc Petit", "luc.petit@example.com", false));
|
||||
sampleUsers.add(new SampleUser("Anne Dubois", "anne.dubois@example.com", true));
|
||||
sampleUsers.add(new SampleUser("Paul Thomas", "paul.thomas@example.com", true));
|
||||
sampleUsers.add(new SampleUser("Claire Robert", "claire.robert@example.com", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialiser l'arborescence pour tree et treeTable
|
||||
*/
|
||||
private void initTreeData() {
|
||||
treeRoot = new org.primefaces.model.DefaultTreeNode<>(new NodeData("Racine", "Dossier"), null);
|
||||
|
||||
TreeNode<NodeData> documents = new org.primefaces.model.DefaultTreeNode<>(
|
||||
new NodeData("Documents", "Dossier"), treeRoot);
|
||||
TreeNode<NodeData> images = new org.primefaces.model.DefaultTreeNode<>(
|
||||
new NodeData("Images", "Dossier"), treeRoot);
|
||||
|
||||
new org.primefaces.model.DefaultTreeNode<>(new NodeData("Rapport.pdf", "Fichier"), documents);
|
||||
new org.primefaces.model.DefaultTreeNode<>(new NodeData("Facture.xlsx", "Fichier"), documents);
|
||||
new org.primefaces.model.DefaultTreeNode<>(new NodeData("Photo1.jpg", "Fichier"), images);
|
||||
new org.primefaces.model.DefaultTreeNode<>(new NodeData("Photo2.png", "Fichier"), images);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialiser les données du graphique Chart.js (format JSON moderne)
|
||||
*/
|
||||
private void initChartData() {
|
||||
// Utilisation de JSON direct pour Chart.js (API moderne, non dépréciée)
|
||||
chartData = """
|
||||
{
|
||||
"type": "bar",
|
||||
"data": {
|
||||
"labels": ["Jan", "Fév", "Mar", "Avr", "Mai", "Juin"],
|
||||
"datasets": [{
|
||||
"label": "Statistiques Mensuelles",
|
||||
"data": [65, 59, 80, 81, 56, 55],
|
||||
"backgroundColor": [
|
||||
"rgba(54, 162, 235, 0.2)",
|
||||
"rgba(75, 192, 192, 0.2)",
|
||||
"rgba(255, 206, 86, 0.2)",
|
||||
"rgba(153, 102, 255, 0.2)",
|
||||
"rgba(255, 99, 132, 0.2)",
|
||||
"rgba(255, 159, 64, 0.2)"
|
||||
],
|
||||
"borderColor": [
|
||||
"rgba(54, 162, 235, 1)",
|
||||
"rgba(75, 192, 192, 1)",
|
||||
"rgba(255, 206, 86, 1)",
|
||||
"rgba(153, 102, 255, 1)",
|
||||
"rgba(255, 99, 132, 1)",
|
||||
"rgba(255, 159, 64, 1)"
|
||||
],
|
||||
"borderWidth": 1
|
||||
}]
|
||||
},
|
||||
"options": {
|
||||
"responsive": true,
|
||||
"maintainAspectRatio": false,
|
||||
"plugins": {
|
||||
"legend": {
|
||||
"display": true,
|
||||
"position": "top"
|
||||
}
|
||||
},
|
||||
"scales": {
|
||||
"y": {
|
||||
"beginAtZero": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
/**
|
||||
* Autocomplétion pour fieldAutoComplete
|
||||
*/
|
||||
public List<String> completeCities(String query) {
|
||||
List<String> allCities = Arrays.asList(
|
||||
"Paris", "Lyon", "Marseille", "Toulouse", "Nice", "Nantes",
|
||||
"Strasbourg", "Montpellier", "Bordeaux", "Lille", "Rennes", "Reims"
|
||||
);
|
||||
|
||||
return allCities.stream()
|
||||
.filter(city -> city.toLowerCase().startsWith(query.toLowerCase()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
// ============ ACTIONS ============
|
||||
|
||||
public void saveAction() {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", "Données sauvegardées avec succès"));
|
||||
LOGGER.info("Action Save executée");
|
||||
}
|
||||
|
||||
public void cancelAction() {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_WARN, "Annulé", "Action annulée"));
|
||||
LOGGER.info("Action Cancel executée");
|
||||
}
|
||||
|
||||
public void refreshAction() {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Rafraîchi", "Données rafraîchies"));
|
||||
LOGGER.info("Action Refresh executée");
|
||||
}
|
||||
|
||||
public void confirmAction() {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Confirmé", "Action confirmée avec succès"));
|
||||
LOGGER.info("Action Confirm executée (actionDialog)");
|
||||
}
|
||||
|
||||
public void createUser() {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Utilisateur créé",
|
||||
"Utilisateur " + dialogData.getNom() + " créé avec succès"));
|
||||
LOGGER.info("Utilisateur créé: " + dialogData.getNom() + " - " + dialogData.getEmail());
|
||||
dialogData = new FormDialogData(); // Reset
|
||||
}
|
||||
|
||||
public void showSuccessMessage() {
|
||||
FacesContext.getCurrentInstance().addMessage("growlDemo",
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", "Opération réussie avec succès"));
|
||||
}
|
||||
|
||||
public void showWarningMessage() {
|
||||
FacesContext.getCurrentInstance().addMessage("growlDemo",
|
||||
new FacesMessage(FacesMessage.SEVERITY_WARN, "Attention", "Ceci est un avertissement"));
|
||||
}
|
||||
|
||||
public void startProgress() {
|
||||
progressValue = 0;
|
||||
Timer timer = new Timer();
|
||||
timer.schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
progressValue = Math.min(100, progressValue + 10);
|
||||
if (progressValue >= 100) {
|
||||
timer.cancel();
|
||||
}
|
||||
}
|
||||
}, 0, 500);
|
||||
}
|
||||
|
||||
public void resetProgress() {
|
||||
progressValue = 0;
|
||||
}
|
||||
|
||||
// ============ CLASSES INTERNES ============
|
||||
|
||||
/**
|
||||
* Classe User pour démonstration des champs de formulaire
|
||||
*/
|
||||
@Data
|
||||
public static class User implements Serializable {
|
||||
private String nom;
|
||||
private String password;
|
||||
private String description;
|
||||
private Integer age;
|
||||
private LocalDate dateNaissance;
|
||||
private String pays;
|
||||
private List<String> competences;
|
||||
private Boolean actif;
|
||||
private String genre;
|
||||
private Boolean newsletter;
|
||||
private Boolean modeNuit;
|
||||
private Integer volume;
|
||||
private Integer rating;
|
||||
private List<String> tags;
|
||||
private String couleur;
|
||||
private String bio;
|
||||
private String telephone;
|
||||
private String ville;
|
||||
private Integer quantite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe SampleUser pour dataTable et dataView
|
||||
*/
|
||||
@Data
|
||||
public static class SampleUser implements Serializable {
|
||||
private String nom;
|
||||
private String email;
|
||||
private Boolean actif;
|
||||
|
||||
public SampleUser(String nom, String email, Boolean actif) {
|
||||
this.nom = nom;
|
||||
this.email = email;
|
||||
this.actif = actif;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe NodeData pour tree et treeTable
|
||||
*/
|
||||
@Data
|
||||
public static class NodeData implements Serializable {
|
||||
private String label;
|
||||
private String type;
|
||||
|
||||
public NodeData(String label, String type) {
|
||||
this.label = label;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classe FormDialogData pour formDialog
|
||||
*/
|
||||
@Data
|
||||
public static class FormDialogData implements Serializable {
|
||||
private String nom;
|
||||
private String email;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Bean JSF pour la gestion des affectations de realms
|
||||
* Permet d'assigner des realms aux utilisateurs pour le contrôle d'accès multi-tenant
|
||||
* Permet d'assigner des realms aux utilisateurs pour le contrôle d'accès
|
||||
* multi-tenant
|
||||
*
|
||||
* @author Lions User Manager
|
||||
* @version 1.0.0
|
||||
@@ -61,9 +62,9 @@ public class RealmAssignmentBean implements Serializable {
|
||||
|
||||
// Pour la création/édition
|
||||
private RealmAssignmentDTO newAssignment = RealmAssignmentDTO.builder()
|
||||
.active(true)
|
||||
.temporaire(false)
|
||||
.build();
|
||||
.active(true)
|
||||
.temporaire(false)
|
||||
.build();
|
||||
private String selectedUserId;
|
||||
private String selectedRealmName;
|
||||
|
||||
@@ -113,7 +114,8 @@ public class RealmAssignmentBean implements Serializable {
|
||||
public void loadAvailableUsers() {
|
||||
try {
|
||||
LOGGER.info("Chargement des utilisateurs disponibles");
|
||||
// Charger les utilisateurs du realm lions-user-manager (page 0, 100 utilisateurs max)
|
||||
// Charger les utilisateurs du realm lions-user-manager (page 0, 100
|
||||
// utilisateurs max)
|
||||
UserSearchResultDTO result = userServiceClient.getAllUsers("lions-user-manager", 0, 100);
|
||||
availableUsers = result != null && result.getUsers() != null ? result.getUsers() : new ArrayList<>();
|
||||
LOGGER.info("Chargement réussi: " + availableUsers.size() + " utilisateur(s) disponible(s)");
|
||||
@@ -132,7 +134,7 @@ public class RealmAssignmentBean implements Serializable {
|
||||
try {
|
||||
LOGGER.info("Chargement des realms disponibles depuis Keycloak");
|
||||
List<String> realms = realmServiceClient.getAllRealms();
|
||||
|
||||
|
||||
if (realms == null || realms.isEmpty()) {
|
||||
LOGGER.warning("Aucun realm trouvé dans Keycloak");
|
||||
availableRealms = Collections.emptyList();
|
||||
@@ -168,9 +170,9 @@ public class RealmAssignmentBean implements Serializable {
|
||||
|
||||
// Trouver l'utilisateur sélectionné
|
||||
UserDTO selectedUser = availableUsers.stream()
|
||||
.filter(u -> u.getId().equals(selectedUserId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
.filter(u -> u.getId().equals(selectedUserId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (selectedUser == null) {
|
||||
addErrorMessage("Utilisateur introuvable");
|
||||
@@ -179,23 +181,24 @@ public class RealmAssignmentBean implements Serializable {
|
||||
|
||||
// Construire l'assignation
|
||||
RealmAssignmentDTO assignment = RealmAssignmentDTO.builder()
|
||||
.userId(selectedUserId)
|
||||
.username(selectedUser.getUsername())
|
||||
.email(selectedUser.getEmail())
|
||||
.realmName(selectedRealmName)
|
||||
.isSuperAdmin(false)
|
||||
.assignedAt(LocalDateTime.now())
|
||||
.assignedBy(userSessionBean.getUsername())
|
||||
.raison(newAssignment.getRaison())
|
||||
.commentaires(newAssignment.getCommentaires())
|
||||
.temporaire(newAssignment.getTemporaire() != null && newAssignment.getTemporaire())
|
||||
.dateExpiration(newAssignment.getDateExpiration())
|
||||
.active(true)
|
||||
.build();
|
||||
.userId(selectedUserId)
|
||||
.username(selectedUser.getUsername())
|
||||
.email(selectedUser.getEmail())
|
||||
.realmName(selectedRealmName)
|
||||
.isSuperAdmin(false)
|
||||
.assignedAt(LocalDateTime.now())
|
||||
.assignedBy(userSessionBean.getUsername())
|
||||
.raison(newAssignment.getRaison())
|
||||
.commentaires(newAssignment.getCommentaires())
|
||||
.temporaire(newAssignment.getTemporaire() != null && newAssignment.getTemporaire())
|
||||
.dateExpiration(newAssignment.getDateExpiration())
|
||||
.active(true)
|
||||
.build();
|
||||
|
||||
LOGGER.info("Assignation du realm " + selectedRealmName + " à l'utilisateur " + selectedUser.getUsername());
|
||||
|
||||
RealmAssignmentDTO created = realmAssignmentServiceClient.assignRealmToUser(assignment);
|
||||
jakarta.ws.rs.core.Response response = realmAssignmentServiceClient.assignRealmToUser(assignment);
|
||||
response.readEntity(RealmAssignmentDTO.class);
|
||||
|
||||
addSuccessMessage("Realm '" + selectedRealmName + "' assigné avec succès à " + selectedUser.getUsername());
|
||||
resetForm();
|
||||
@@ -219,11 +222,13 @@ public class RealmAssignmentBean implements Serializable {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Révocation du realm " + assignment.getRealmName() + " pour l'utilisateur " + assignment.getUsername());
|
||||
LOGGER.info("Révocation du realm " + assignment.getRealmName() + " pour l'utilisateur "
|
||||
+ assignment.getUsername());
|
||||
|
||||
realmAssignmentServiceClient.revokeRealmFromUser(assignment.getUserId(), assignment.getRealmName());
|
||||
|
||||
addSuccessMessage("Accès révoqué pour " + assignment.getUsername() + " au realm '" + assignment.getRealmName() + "'");
|
||||
addSuccessMessage(
|
||||
"Accès révoqué pour " + assignment.getUsername() + " au realm '" + assignment.getRealmName() + "'");
|
||||
loadAssignments();
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -293,9 +298,9 @@ public class RealmAssignmentBean implements Serializable {
|
||||
}
|
||||
|
||||
UserDTO user = availableUsers.stream()
|
||||
.filter(u -> u.getId().equals(userId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
.filter(u -> u.getId().equals(userId))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
String username = user != null ? user.getUsername() : userId;
|
||||
|
||||
@@ -323,9 +328,9 @@ public class RealmAssignmentBean implements Serializable {
|
||||
*/
|
||||
public void resetForm() {
|
||||
newAssignment = RealmAssignmentDTO.builder()
|
||||
.active(true)
|
||||
.temporaire(false)
|
||||
.build();
|
||||
.active(true)
|
||||
.temporaire(false)
|
||||
.build();
|
||||
selectedUserId = null;
|
||||
selectedRealmName = null;
|
||||
}
|
||||
@@ -339,16 +344,18 @@ public class RealmAssignmentBean implements Serializable {
|
||||
}
|
||||
|
||||
return assignments.stream()
|
||||
.filter(a -> {
|
||||
boolean matchUser = filterUserName == null || filterUserName.isEmpty() ||
|
||||
(a.getUsername() != null && a.getUsername().toLowerCase().contains(filterUserName.toLowerCase()));
|
||||
.filter(a -> {
|
||||
boolean matchUser = filterUserName == null || filterUserName.isEmpty() ||
|
||||
(a.getUsername() != null
|
||||
&& a.getUsername().toLowerCase().contains(filterUserName.toLowerCase()));
|
||||
|
||||
boolean matchRealm = filterRealmName == null || filterRealmName.isEmpty() ||
|
||||
(a.getRealmName() != null && a.getRealmName().toLowerCase().contains(filterRealmName.toLowerCase()));
|
||||
boolean matchRealm = filterRealmName == null || filterRealmName.isEmpty() ||
|
||||
(a.getRealmName() != null
|
||||
&& a.getRealmName().toLowerCase().contains(filterRealmName.toLowerCase()));
|
||||
|
||||
return matchUser && matchRealm;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return matchUser && matchRealm;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -363,8 +370,8 @@ public class RealmAssignmentBean implements Serializable {
|
||||
*/
|
||||
public long getActiveAssignmentsCount() {
|
||||
return assignments.stream()
|
||||
.filter(RealmAssignmentDTO::isActive)
|
||||
.count();
|
||||
.filter(RealmAssignmentDTO::isActive)
|
||||
.count();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -372,23 +379,23 @@ public class RealmAssignmentBean implements Serializable {
|
||||
*/
|
||||
public long getSuperAdminsCount() {
|
||||
return assignments.stream()
|
||||
.filter(RealmAssignmentDTO::isSuperAdmin)
|
||||
.count();
|
||||
.filter(RealmAssignmentDTO::isSuperAdmin)
|
||||
.count();
|
||||
}
|
||||
|
||||
// Méthodes utilitaires pour les messages
|
||||
private void addSuccessMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
}
|
||||
|
||||
private void addErrorMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
}
|
||||
|
||||
private void addInfoMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Information", message));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package dev.lions.user.manager.client.view;
|
||||
|
||||
import dev.lions.user.manager.client.service.RealmServiceClient;
|
||||
import dev.lions.user.manager.client.service.RoleServiceClient;
|
||||
import dev.lions.user.manager.client.service.RealmServiceClient;
|
||||
import dev.lions.user.manager.dto.role.RoleAssignmentDTO;
|
||||
import dev.lions.user.manager.dto.role.RoleDTO;
|
||||
import dev.lions.user.manager.dto.user.UserDTO;
|
||||
@@ -17,7 +17,6 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -43,21 +42,20 @@ public class RoleGestionBean implements Serializable {
|
||||
@RestClient
|
||||
private RealmServiceClient realmServiceClient;
|
||||
|
||||
@Inject
|
||||
private UserSessionBean userSessionBean;
|
||||
|
||||
// Liste des rôles
|
||||
private List<RoleDTO> realmRoles = new ArrayList<>();
|
||||
private List<RoleDTO> clientRoles = new ArrayList<>();
|
||||
private List<RoleDTO> allRoles = new ArrayList<>();
|
||||
private RoleDTO selectedRole;
|
||||
private String selectedRoleName;
|
||||
|
||||
// Pour la création/édition
|
||||
private RoleDTO newRole = RoleDTO.builder().build();
|
||||
private boolean editMode = false;
|
||||
|
||||
// Filtres
|
||||
// Par défaut, utiliser le realm lions-user-manager où les rôles métier sont configurés
|
||||
// Par défaut, utiliser le realm lions-user-manager où les utilisateurs sont
|
||||
// configurés
|
||||
private String realmName = "lions-user-manager";
|
||||
private String clientName;
|
||||
private TypeRole selectedTypeRole;
|
||||
@@ -83,7 +81,7 @@ public class RoleGestionBean implements Serializable {
|
||||
addErrorMessage("Veuillez sélectionner un realm");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
LOGGER.info("Chargement des rôles Realm pour le realm: " + realmName);
|
||||
realmRoles = roleServiceClient.getAllRealmRoles(realmName);
|
||||
@@ -93,7 +91,8 @@ public class RoleGestionBean implements Serializable {
|
||||
addErrorMessage("Aucun rôle Realm trouvé dans le realm: " + realmName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String errorMsg = "Erreur lors du chargement des rôles Realm pour le realm '" + realmName + "': " + e.getMessage();
|
||||
String errorMsg = "Erreur lors du chargement des rôles Realm pour le realm '" + realmName + "': "
|
||||
+ e.getMessage();
|
||||
LOGGER.severe(errorMsg);
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Exception complète", e);
|
||||
addErrorMessage(errorMsg);
|
||||
@@ -111,7 +110,7 @@ public class RoleGestionBean implements Serializable {
|
||||
}
|
||||
|
||||
try {
|
||||
clientRoles = roleServiceClient.getAllClientRoles(clientName, realmName);
|
||||
clientRoles = roleServiceClient.getAllClientRoles(realmName, clientName);
|
||||
updateAllRoles();
|
||||
LOGGER.info("Chargement de " + clientRoles.size() + " rôles Client");
|
||||
} catch (Exception e) {
|
||||
@@ -134,7 +133,8 @@ public class RoleGestionBean implements Serializable {
|
||||
*/
|
||||
public void createRealmRole() {
|
||||
try {
|
||||
RoleDTO created = roleServiceClient.createRealmRole(newRole, realmName);
|
||||
jakarta.ws.rs.core.Response response = roleServiceClient.createRealmRole(newRole, realmName);
|
||||
RoleDTO created = response.readEntity(RoleDTO.class);
|
||||
addSuccessMessage("Rôle Realm créé avec succès: " + created.getName());
|
||||
resetForm();
|
||||
loadRealmRoles();
|
||||
@@ -154,7 +154,8 @@ public class RoleGestionBean implements Serializable {
|
||||
}
|
||||
|
||||
try {
|
||||
RoleDTO created = roleServiceClient.createClientRole(clientName, newRole, realmName);
|
||||
jakarta.ws.rs.core.Response response = roleServiceClient.createClientRole(clientName, newRole, realmName);
|
||||
RoleDTO created = response.readEntity(RoleDTO.class);
|
||||
addSuccessMessage("Rôle Client créé avec succès: " + created.getName());
|
||||
resetForm();
|
||||
loadClientRoles();
|
||||
@@ -202,9 +203,10 @@ public class RoleGestionBean implements Serializable {
|
||||
*/
|
||||
public void assignRoleToUser(String userId, String roleName) {
|
||||
try {
|
||||
RoleServiceClient.RoleAssignmentRequest request = new RoleServiceClient.RoleAssignmentRequest();
|
||||
request.roleNames = List.of(roleName);
|
||||
roleServiceClient.assignRealmRolesToUser(userId, realmName, request);
|
||||
dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO request = new dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO(
|
||||
List.of(roleName));
|
||||
|
||||
roleServiceClient.assignRealmRoles(userId, realmName, request);
|
||||
addSuccessMessage("Rôle attribué avec succès");
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'attribution: " + e.getMessage());
|
||||
@@ -217,9 +219,10 @@ public class RoleGestionBean implements Serializable {
|
||||
*/
|
||||
public void revokeRoleFromUser(String userId, String roleName) {
|
||||
try {
|
||||
RoleServiceClient.RoleAssignmentRequest request = new RoleServiceClient.RoleAssignmentRequest();
|
||||
request.roleNames = List.of(roleName);
|
||||
roleServiceClient.revokeRealmRolesFromUser(userId, realmName, request);
|
||||
dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO request = new dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO(
|
||||
List.of(roleName));
|
||||
|
||||
roleServiceClient.revokeRealmRoles(userId, realmName, request);
|
||||
addSuccessMessage("Rôle révoqué avec succès");
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la révocation: " + e.getMessage());
|
||||
@@ -234,7 +237,7 @@ public class RoleGestionBean implements Serializable {
|
||||
FacesContext context = FacesContext.getCurrentInstance();
|
||||
String userId = context.getExternalContext().getRequestParameterMap().get("userId");
|
||||
String roleName = context.getExternalContext().getRequestParameterMap().get("roleName");
|
||||
|
||||
|
||||
if (userId != null && roleName != null) {
|
||||
assignRoleToUser(userId, roleName);
|
||||
} else {
|
||||
@@ -249,7 +252,7 @@ public class RoleGestionBean implements Serializable {
|
||||
FacesContext context = FacesContext.getCurrentInstance();
|
||||
String userId = context.getExternalContext().getRequestParameterMap().get("userId");
|
||||
String roleName = context.getExternalContext().getRequestParameterMap().get("roleName");
|
||||
|
||||
|
||||
if (userId != null && roleName != null) {
|
||||
revokeRoleFromUser(userId, roleName);
|
||||
} else {
|
||||
@@ -264,14 +267,14 @@ public class RoleGestionBean implements Serializable {
|
||||
if (user == null || user.getRealmRoles() == null || user.getRealmRoles().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
if (allRoles == null || allRoles.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
|
||||
return allRoles.stream()
|
||||
.filter(role -> user.getRealmRoles().contains(role.getName()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
.filter(role -> user.getRealmRoles().contains(role.getName()))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,58 +286,45 @@ public class RoleGestionBean implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Charger les realms disponibles depuis Keycloak en fonction des permissions de l'utilisateur
|
||||
* Charger les realms disponibles
|
||||
*/
|
||||
/**
|
||||
* Charger les realms disponibles depuis Keycloak
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
availableRealms = realmServiceClient.getAllRealms();
|
||||
LOGGER.info("Realms chargés: " + availableRealms);
|
||||
} 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: liste vide plutôt que des données fictives
|
||||
availableRealms = Collections.emptyList();
|
||||
LOGGER.severe("Erreur lors du chargement des realms: " + e.getMessage());
|
||||
// Fallback en cas d'erreur
|
||||
availableRealms = List.of("master", "lions-user-manager", "btpxpress", "unionflow");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Charger les rôles du realm spécifié.
|
||||
* Cette méthode sert de point d'entrée pour la page d'édition.
|
||||
*
|
||||
* @param realm Le nom du realm dont il faut charger les rôles
|
||||
*/
|
||||
public void loadRolesForUser(String realm) {
|
||||
if (realm != null && !realm.isEmpty()) {
|
||||
this.realmName = realm;
|
||||
loadRealmRoles();
|
||||
} else {
|
||||
// Utiliser le realm par défaut si non fourni
|
||||
loadRealmRoles();
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes utilitaires
|
||||
private void addSuccessMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
}
|
||||
|
||||
private void addErrorMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package dev.lions.user.manager.client.view;
|
||||
|
||||
import jakarta.enterprise.context.SessionScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
@@ -27,9 +25,6 @@ public class SessionMonitorBean implements Serializable {
|
||||
// Temps d'inactivité maximum en secondes (30 minutes par défaut)
|
||||
private static final long DEFAULT_INACTIVITY_TIMEOUT = 1800;
|
||||
|
||||
@Inject
|
||||
private JsonWebToken jwt;
|
||||
|
||||
private Instant lastActivityTime;
|
||||
private long inactivityTimeout = DEFAULT_INACTIVITY_TIMEOUT;
|
||||
|
||||
@@ -94,7 +89,8 @@ public class SessionMonitorBean implements Serializable {
|
||||
*/
|
||||
public int getSessionProgressPercent() {
|
||||
long inactivitySeconds = getInactivitySeconds();
|
||||
if (inactivityTimeout == 0) return 0;
|
||||
if (inactivityTimeout == 0)
|
||||
return 0;
|
||||
|
||||
int percent = (int) ((inactivitySeconds * 100) / inactivityTimeout);
|
||||
return Math.min(100, Math.max(0, percent));
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package dev.lions.user.manager.client.view;
|
||||
|
||||
import dev.lions.user.manager.client.service.SyncServiceClient;
|
||||
import dev.lions.user.manager.dto.sync.HealthStatusDTO;
|
||||
import dev.lions.user.manager.dto.sync.SyncResultDTO;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.faces.application.FacesMessage;
|
||||
import jakarta.faces.context.FacesContext;
|
||||
import jakarta.faces.view.ViewScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class SyncDashboardBean implements Serializable {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(SyncDashboardBean.class.getName());
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
SyncServiceClient syncService;
|
||||
|
||||
private HealthStatusDTO keycloakStatus;
|
||||
private String syncMessage;
|
||||
private String targetRealm = "lions-user-manager"; // Default realm matches OIDC config
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
checkKeycloakStatus();
|
||||
}
|
||||
|
||||
public void checkKeycloakStatus() {
|
||||
try {
|
||||
this.keycloakStatus = syncService.checkKeycloakHealth();
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la vérification de l'état de Keycloak: " + e.getMessage());
|
||||
this.keycloakStatus = new HealthStatusDTO();
|
||||
this.keycloakStatus.setOverallHealthy(false);
|
||||
this.keycloakStatus.setErrorMessage("Erreur de connexion: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void syncUsers() {
|
||||
try {
|
||||
SyncResultDTO result = syncService.syncUsers(targetRealm);
|
||||
handleSyncResult("Utilisateurs", result);
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la synchronisation des utilisateurs: " + e.getMessage());
|
||||
addMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Echec de la synchronisation des utilisateurs: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void syncRoles() {
|
||||
try {
|
||||
// Sychronize realm roles
|
||||
SyncResultDTO result = syncService.syncRoles(targetRealm, null);
|
||||
handleSyncResult("Rôles", result);
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la synchronisation des rôles: " + e.getMessage());
|
||||
addMessage(FacesMessage.SEVERITY_ERROR, "Erreur",
|
||||
"Echec de la synchronisation des rôles: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSyncResult(String type, SyncResultDTO result) {
|
||||
String msg = result.isSuccess()
|
||||
? "Synchronisation réussie. " + type + " synchronisés : "
|
||||
+ ("Utilisateurs".equals(type) ? result.getUsersCount() : result.getRealmRolesCount())
|
||||
: result.getErrorMessage();
|
||||
|
||||
if (result.isSuccess()) {
|
||||
addMessage(FacesMessage.SEVERITY_INFO, "Succès", msg);
|
||||
} else {
|
||||
addMessage(FacesMessage.SEVERITY_WARN, "Attention", type + ": " + msg);
|
||||
}
|
||||
this.syncMessage = msg;
|
||||
}
|
||||
|
||||
private void addMessage(FacesMessage.Severity severity, String summary, String detail) {
|
||||
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(severity, summary, detail));
|
||||
}
|
||||
|
||||
// Getters and Setters
|
||||
public HealthStatusDTO getKeycloakStatus() {
|
||||
return keycloakStatus;
|
||||
}
|
||||
|
||||
public void setKeycloakStatus(HealthStatusDTO keycloakStatus) {
|
||||
this.keycloakStatus = keycloakStatus;
|
||||
}
|
||||
|
||||
// Convenience methods for view
|
||||
public String getKeycloakStatusLabel() {
|
||||
return (keycloakStatus != null && keycloakStatus.isOverallHealthy()) ? "UP" : "DOWN";
|
||||
}
|
||||
|
||||
public String getKeycloakStatusMessage() {
|
||||
if (keycloakStatus == null)
|
||||
return "Unknown status";
|
||||
if (keycloakStatus.isOverallHealthy())
|
||||
return "Keycloak est accessible.";
|
||||
return keycloakStatus.getErrorMessage() != null ? keycloakStatus.getErrorMessage() : "Erreur inconnue";
|
||||
}
|
||||
|
||||
public String getKeycloakVersion() {
|
||||
return (keycloakStatus != null) ? keycloakStatus.getKeycloakVersion() : "N/A";
|
||||
}
|
||||
|
||||
public String getSyncMessage() {
|
||||
return syncMessage;
|
||||
}
|
||||
|
||||
public void setSyncMessage(String syncMessage) {
|
||||
this.syncMessage = syncMessage;
|
||||
}
|
||||
|
||||
public String getTargetRealm() {
|
||||
return targetRealm;
|
||||
}
|
||||
|
||||
public void setTargetRealm(String targetRealm) {
|
||||
this.targetRealm = targetRealm;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package dev.lions.user.manager.client.view;
|
||||
|
||||
import dev.lions.user.manager.client.service.RealmServiceClient;
|
||||
import dev.lions.user.manager.client.service.UserServiceClient;
|
||||
import dev.lions.user.manager.dto.user.UserDTO;
|
||||
import dev.lions.user.manager.enums.user.StatutUser;
|
||||
@@ -15,7 +14,6 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -37,13 +35,10 @@ public class UserCreationBean implements Serializable {
|
||||
@RestClient
|
||||
private UserServiceClient userServiceClient;
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
private RealmServiceClient realmServiceClient;
|
||||
|
||||
private UserDTO newUser = UserDTO.builder().build();
|
||||
// Par défaut, utiliser le realm lions-user-manager où les utilisateurs sont configurés
|
||||
private String realmName = "lions-user-manager";
|
||||
// Le realm "master" est le realm d'administration qui permet de gérer tous les
|
||||
// autres realms
|
||||
private String realmName = "master";
|
||||
private String password;
|
||||
private String passwordConfirm;
|
||||
|
||||
@@ -82,13 +77,13 @@ public class UserCreationBean implements Serializable {
|
||||
|
||||
try {
|
||||
// Créer l'utilisateur
|
||||
UserDTO createdUser = userServiceClient.createUser(newUser, realmName);
|
||||
jakarta.ws.rs.core.Response response = userServiceClient.createUser(newUser, realmName);
|
||||
UserDTO createdUser = response.readEntity(UserDTO.class);
|
||||
|
||||
// Définir le mot de passe
|
||||
UserServiceClient.PasswordResetRequest passwordRequest = new UserServiceClient.PasswordResetRequest();
|
||||
passwordRequest.password = password;
|
||||
passwordRequest.temporary = true;
|
||||
userServiceClient.resetPassword(createdUser.getId(), realmName, passwordRequest);
|
||||
dev.lions.user.manager.dto.user.PasswordResetRequestDTO request = new dev.lions.user.manager.dto.user.PasswordResetRequestDTO(
|
||||
password, false);
|
||||
userServiceClient.resetPassword(createdUser.getId(), realmName, request);
|
||||
|
||||
addSuccessMessage("Utilisateur créé avec succès: " + createdUser.getUsername());
|
||||
resetForm();
|
||||
@@ -121,68 +116,23 @@ public class UserCreationBean implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Charger les realms disponibles
|
||||
*/
|
||||
private void loadRealms() {
|
||||
try {
|
||||
LOGGER.info("Chargement des realms disponibles depuis Keycloak");
|
||||
List<String> realms = realmServiceClient.getAllRealms();
|
||||
|
||||
if (realms == null || realms.isEmpty()) {
|
||||
LOGGER.warning("Aucun realm trouvé dans Keycloak");
|
||||
availableRealms = Collections.emptyList();
|
||||
} else {
|
||||
availableRealms = new ArrayList<>(realms);
|
||||
LOGGER.info("Realms disponibles chargés depuis Keycloak: " + availableRealms.size());
|
||||
|
||||
// Définir le premier realm comme realm par défaut si aucun n'est sélectionné
|
||||
if (!availableRealms.isEmpty() && (realmName == null || realmName.isEmpty())) {
|
||||
realmName = availableRealms.get(0);
|
||||
}
|
||||
}
|
||||
} 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: liste vide plutôt que des données fictives
|
||||
availableRealms = Collections.emptyList();
|
||||
}
|
||||
// TODO: Implémenter la récupération des realms depuis Keycloak
|
||||
// Le realm "master" est le realm d'administration qui permet de gérer tous les
|
||||
// autres realms
|
||||
availableRealms = List.of("master", "lions-user-manager", "btpxpress", "unionflow");
|
||||
}
|
||||
|
||||
// Méthodes utilitaires
|
||||
private void addSuccessMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
}
|
||||
|
||||
private void addErrorMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
@@ -16,13 +15,16 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import lombok.Data;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.primefaces.PrimeFaces;
|
||||
import org.primefaces.event.data.PageEvent;
|
||||
import org.primefaces.model.FilterMeta;
|
||||
import org.primefaces.model.LazyDataModel;
|
||||
import org.primefaces.model.SortMeta;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
@@ -52,18 +54,16 @@ public class UserListBean implements Serializable {
|
||||
@RestClient
|
||||
private RealmServiceClient realmServiceClient;
|
||||
|
||||
@Inject
|
||||
private UserSessionBean userSessionBean;
|
||||
|
||||
// Propriétés pour la liste
|
||||
private List<UserDTO> users = new ArrayList<>();
|
||||
private LazyDataModel<UserDTO> users;
|
||||
private UserDTO selectedUser;
|
||||
private List<UserDTO> selectedUsers = new ArrayList<>();
|
||||
|
||||
// Propriétés pour la recherche
|
||||
private UserSearchCriteriaDTO searchCriteria = UserSearchCriteriaDTO.builder().build();
|
||||
private String searchText;
|
||||
// Par défaut, utiliser le realm lions-user-manager où les utilisateurs sont configurés
|
||||
// Par défaut, utiliser le realm lions-user-manager où les utilisateurs sont
|
||||
// configurés
|
||||
private String realmName = "lions-user-manager";
|
||||
private StatutUser selectedStatut;
|
||||
|
||||
@@ -77,63 +77,84 @@ 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;
|
||||
// Champs pour réinitialisation mot de passe
|
||||
private String newPassword;
|
||||
private String newPasswordConfirm;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
LOGGER.info("Initialisation de UserListBean");
|
||||
loadUsers();
|
||||
loadRealms();
|
||||
|
||||
users = new LazyDataModel<UserDTO>() {
|
||||
@Override
|
||||
public String getRowKey(UserDTO user) {
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDTO getRowData(String rowKey) {
|
||||
List<UserDTO> list = (List<UserDTO>) getWrappedData();
|
||||
if (list != null) {
|
||||
for (UserDTO user : list) {
|
||||
if (user.getId().equals(rowKey)) {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count(Map<String, FilterMeta> filterBy) {
|
||||
return (int) totalRecords;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserDTO> load(int first, int pageSize, Map<String, SortMeta> sortBy,
|
||||
Map<String, FilterMeta> filterBy) {
|
||||
try {
|
||||
int page = first / pageSize;
|
||||
|
||||
UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
|
||||
.realmName(realmName)
|
||||
.searchTerm(searchText)
|
||||
.statut(selectedStatut)
|
||||
.page(page)
|
||||
.pageSize(pageSize)
|
||||
.build();
|
||||
|
||||
UserSearchResultDTO result = userServiceClient.searchUsers(criteria);
|
||||
List<UserDTO> data = result.getUsers() != null ? result.getUsers() : new ArrayList<>();
|
||||
|
||||
long total = result.getTotalCount() != null ? result.getTotalCount() : 0;
|
||||
this.setRowCount((int) total);
|
||||
totalRecords = total;
|
||||
|
||||
return data;
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur loading: " + e.getMessage());
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Charger la liste des utilisateurs
|
||||
* (Deprecated: Utiliser LazyDataModel)
|
||||
*/
|
||||
public void loadUsers() {
|
||||
try {
|
||||
UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
|
||||
.realmName(realmName)
|
||||
.page(currentPage)
|
||||
.pageSize(pageSize)
|
||||
.build();
|
||||
|
||||
UserSearchResultDTO result = userServiceClient.searchUsers(criteria);
|
||||
this.users = result.getUsers() != null ? result.getUsers() : new ArrayList<>();
|
||||
this.totalRecords = result.getTotalCount() != null ? result.getTotalCount() : 0;
|
||||
this.totalPages = (int) Math.ceil((double) totalRecords / pageSize);
|
||||
|
||||
LOGGER.info("Chargement de " + users.size() + " utilisateurs");
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du chargement des utilisateurs: " + e.getMessage());
|
||||
addErrorMessage("Erreur lors du chargement des utilisateurs: " + e.getMessage());
|
||||
}
|
||||
// La méthode load du LazyDataModel est appelée automatiquement par le composant
|
||||
}
|
||||
|
||||
/**
|
||||
* Rechercher des utilisateurs
|
||||
*/
|
||||
public void search() {
|
||||
try {
|
||||
currentPage = 0; // Réinitialiser à la première page
|
||||
|
||||
UserSearchCriteriaDTO criteria = UserSearchCriteriaDTO.builder()
|
||||
.realmName(realmName)
|
||||
.searchTerm(searchText)
|
||||
.statut(selectedStatut)
|
||||
.page(currentPage)
|
||||
.pageSize(pageSize)
|
||||
.build();
|
||||
|
||||
UserSearchResultDTO result = userServiceClient.searchUsers(criteria);
|
||||
this.users = result.getUsers() != null ? result.getUsers() : new ArrayList<>();
|
||||
this.totalRecords = result.getTotalCount() != null ? result.getTotalCount() : 0;
|
||||
this.totalPages = (int) Math.ceil((double) totalRecords / pageSize);
|
||||
|
||||
addSuccessMessage("Recherche effectuée: " + totalRecords + " résultat(s) trouvé(s)");
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la recherche: " + e.getMessage());
|
||||
addErrorMessage("Erreur lors de la recherche: " + e.getMessage());
|
||||
currentPage = 0;
|
||||
if (PrimeFaces.current().isAjaxRequest()) {
|
||||
PrimeFaces.current().executeScript("PF('userTableWidget').getPaginator().setPage(0);");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,25 +165,31 @@ public class UserListBean implements Serializable {
|
||||
searchText = null;
|
||||
selectedStatut = null;
|
||||
currentPage = 0;
|
||||
loadUsers();
|
||||
if (PrimeFaces.current().isAjaxRequest()) {
|
||||
PrimeFaces.current().executeScript("PF('userTableWidget').getPaginator().setPage(0);");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gérer les événements de pagination du datatable
|
||||
* Réinitialiser le mot de passe
|
||||
*/
|
||||
public void onPageChange(PageEvent event) {
|
||||
try {
|
||||
int page = event.getPage();
|
||||
|
||||
currentPage = page;
|
||||
|
||||
LOGGER.info("Changement de page: page=" + currentPage + ", rows=" + pageSize);
|
||||
|
||||
// Recharger les données avec la nouvelle page
|
||||
loadUsers();
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors du changement de page: " + e.getMessage());
|
||||
addErrorMessage("Erreur lors du changement de page: " + e.getMessage());
|
||||
public void resetPassword(String userId) {
|
||||
if (newPassword != null && !newPassword.isEmpty() && newPassword.equals(newPasswordConfirm)) {
|
||||
try {
|
||||
// Utilisation du DTO pour la demande de réinitialisation
|
||||
dev.lions.user.manager.dto.user.PasswordResetRequestDTO request = new dev.lions.user.manager.dto.user.PasswordResetRequestDTO(
|
||||
newPassword, false);
|
||||
userServiceClient.resetPassword(userId, realmName, request);
|
||||
addSuccessMessage("Mot de passe réinitialisé avec succès");
|
||||
// Clear fields
|
||||
newPassword = null;
|
||||
newPasswordConfirm = null;
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la réinitialisation du mot de passe: " + e.getMessage());
|
||||
addErrorMessage("Erreur lors de la réinitialisation: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
addErrorMessage("Les mots de passe sont invalides ou ne correspondent pas");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,14 +228,10 @@ public class UserListBean implements Serializable {
|
||||
*/
|
||||
public void activateUser(String userId) {
|
||||
try {
|
||||
LOGGER.info("Activation de l'utilisateur: " + userId + " dans le realm: " + realmName);
|
||||
userServiceClient.activateUser(userId, realmName);
|
||||
addSuccessMessage("Utilisateur activé avec succès");
|
||||
// Recharger les données
|
||||
loadUsers();
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de l'activation: " + e.getMessage());
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Exception complète", e);
|
||||
addErrorMessage("Erreur lors de l'activation: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -218,14 +241,10 @@ public class UserListBean implements Serializable {
|
||||
*/
|
||||
public void deactivateUser(String userId) {
|
||||
try {
|
||||
LOGGER.info("Désactivation de l'utilisateur: " + userId + " dans le realm: " + realmName);
|
||||
userServiceClient.deactivateUser(userId, realmName);
|
||||
userServiceClient.deactivateUser(userId, realmName, "Désactivé par l'administrateur");
|
||||
addSuccessMessage("Utilisateur désactivé avec succès");
|
||||
// Recharger les données
|
||||
loadUsers();
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la désactivation: " + e.getMessage());
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Exception complète", e);
|
||||
addErrorMessage("Erreur lors de la désactivation: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -235,14 +254,10 @@ public class UserListBean implements Serializable {
|
||||
*/
|
||||
public void deleteUser(String userId) {
|
||||
try {
|
||||
LOGGER.info("Suppression de l'utilisateur: " + userId + " dans le realm: " + realmName);
|
||||
userServiceClient.deleteUser(userId, realmName);
|
||||
userServiceClient.deleteUser(userId, realmName, false);
|
||||
addSuccessMessage("Utilisateur supprimé avec succès");
|
||||
// Recharger les données
|
||||
loadUsers();
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la suppression: " + e.getMessage());
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Exception complète", e);
|
||||
addErrorMessage("Erreur lors de la suppression: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -264,24 +279,27 @@ public class UserListBean implements Serializable {
|
||||
* Obtenir le nombre d'utilisateurs actifs
|
||||
*/
|
||||
public long getActiveUsersCount() {
|
||||
if (users == null || users.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return users.stream()
|
||||
.filter(user -> user.getEnabled() != null && user.getEnabled())
|
||||
.count();
|
||||
return calculateStatusCount(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir le nombre d'utilisateurs désactivés
|
||||
*/
|
||||
public long getDisabledUsersCount() {
|
||||
if (users == null || users.isEmpty()) {
|
||||
return calculateStatusCount(false);
|
||||
}
|
||||
|
||||
// Helper for counts
|
||||
private long calculateStatusCount(boolean enabled) {
|
||||
if (users == null)
|
||||
return 0;
|
||||
List<UserDTO> list = (List<UserDTO>) users.getWrappedData();
|
||||
if (list == null || list.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return users.stream()
|
||||
.filter(user -> user.getEnabled() != null && !user.getEnabled())
|
||||
.count();
|
||||
return list.stream()
|
||||
.filter(user -> user.getEnabled() != null && user.getEnabled() == enabled)
|
||||
.count();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,177 +331,33 @@ public class UserListBean implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exporter les utilisateurs en CSV
|
||||
* Exporter vers CSV (placeholder)
|
||||
*/
|
||||
public void exportToCSV() {
|
||||
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());
|
||||
}
|
||||
addSuccessMessage("Fonctionnalité d'export en cours de développement");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Importer des utilisateurs (placeholder)
|
||||
*/
|
||||
public void importUsers() {
|
||||
addInfoMessage("Veuillez utiliser le bouton 'Parcourir' pour sélectionner un fichier CSV");
|
||||
addSuccessMessage("Fonctionnalité d'import en cours de développement");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gérer l'upload de fichier CSV pour import
|
||||
* Charger les realms disponibles
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Charger les realms disponibles depuis Keycloak en fonction des permissions de l'utilisateur
|
||||
* Charger les realms disponibles depuis Keycloak
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
availableRealms = realmServiceClient.getAllRealms();
|
||||
LOGGER.info("Realms chargés: " + availableRealms);
|
||||
} 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: liste vide plutôt que des données fictives
|
||||
availableRealms = Collections.emptyList();
|
||||
LOGGER.severe("Erreur lors du chargement des realms: " + e.getMessage());
|
||||
// Fallback en cas d'erreur
|
||||
availableRealms = List.of("master", "lions-user-manager", "btpxpress", "unionflow");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -511,22 +385,11 @@ public class UserListBean implements Serializable {
|
||||
// Méthodes utilitaires pour les messages
|
||||
private void addSuccessMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
}
|
||||
|
||||
private void addErrorMessage(String message) {
|
||||
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));
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,9 @@ public class UserProfilBean implements Serializable {
|
||||
@RestClient
|
||||
private UserServiceClient userServiceClient;
|
||||
|
||||
@Inject
|
||||
private RoleGestionBean roleGestionBean;
|
||||
|
||||
private UserDTO user;
|
||||
private String userId;
|
||||
// Par défaut, utiliser le realm lions-user-manager où les utilisateurs sont configurés
|
||||
// Le realm "lions-user-manager" est le realm par défaut
|
||||
private String realmName = "lions-user-manager";
|
||||
private boolean editMode = false;
|
||||
|
||||
@@ -47,29 +44,21 @@ public class UserProfilBean implements Serializable {
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// Récupérer l'ID et le realm depuis les paramètres de requête
|
||||
FacesContext facesContext = FacesContext.getCurrentInstance();
|
||||
java.util.Map<String, String> params = facesContext.getExternalContext().getRequestParameterMap();
|
||||
|
||||
userId = params.get("userId");
|
||||
String realmParam = params.get("realm");
|
||||
|
||||
if (realmParam != null && !realmParam.isEmpty()) {
|
||||
realmName = realmParam;
|
||||
}
|
||||
// Récupérer l'ID depuis les paramètres de requête
|
||||
userId = FacesContext.getCurrentInstance().getExternalContext()
|
||||
.getRequestParameterMap().get("userId");
|
||||
|
||||
LOGGER.info("Initialisation de UserProfilBean avec userId: " + userId + ", realm: " + realmName);
|
||||
// Récupérer le realm depuis les paramètres de requête (si présent)
|
||||
String realmParam = FacesContext.getCurrentInstance().getExternalContext()
|
||||
.getRequestParameterMap().get("realm");
|
||||
if (realmParam != null && !realmParam.isEmpty()) {
|
||||
this.realmName = realmParam;
|
||||
}
|
||||
|
||||
if (userId != null && !userId.isEmpty()) {
|
||||
loadUser();
|
||||
// Charger les rôles disponibles
|
||||
if (roleGestionBean != null) {
|
||||
roleGestionBean.setRealmName(realmName);
|
||||
roleGestionBean.loadRealmRoles();
|
||||
}
|
||||
} else {
|
||||
LOGGER.warning("Aucun userId fourni dans les paramètres de requête");
|
||||
addErrorMessage("Aucun ID d'utilisateur fourni. Accédez à cette page depuis la liste des utilisateurs.");
|
||||
LOGGER.warning("Aucun userId fourni dans les paramètres");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,15 +101,11 @@ public class UserProfilBean implements Serializable {
|
||||
*/
|
||||
public void updateUser() {
|
||||
try {
|
||||
LOGGER.info("Mise à jour de l'utilisateur: " + userId + " dans le realm: " + realmName);
|
||||
user = userServiceClient.updateUser(userId, user, realmName);
|
||||
editMode = false;
|
||||
addSuccessMessage("Utilisateur mis à jour avec succès");
|
||||
// Recharger les données pour s'assurer qu'elles sont à jour
|
||||
loadUser();
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la mise à jour: " + e.getMessage());
|
||||
LOGGER.log(java.util.logging.Level.SEVERE, "Exception complète", e);
|
||||
addErrorMessage("Erreur lors de la mise à jour: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -140,9 +125,8 @@ public class UserProfilBean implements Serializable {
|
||||
}
|
||||
|
||||
try {
|
||||
UserServiceClient.PasswordResetRequest request = new UserServiceClient.PasswordResetRequest();
|
||||
request.password = newPassword;
|
||||
request.temporary = true;
|
||||
dev.lions.user.manager.dto.user.PasswordResetRequestDTO request = new dev.lions.user.manager.dto.user.PasswordResetRequestDTO(
|
||||
newPassword, false);
|
||||
userServiceClient.resetPassword(userId, realmName, request);
|
||||
newPassword = null;
|
||||
newPasswordConfirm = null;
|
||||
@@ -172,7 +156,7 @@ public class UserProfilBean implements Serializable {
|
||||
*/
|
||||
public void deactivateUser() {
|
||||
try {
|
||||
userServiceClient.deactivateUser(userId, realmName);
|
||||
userServiceClient.deactivateUser(userId, realmName, "Désactivé depuis le profil utilisateur");
|
||||
loadUser(); // Recharger pour mettre à jour le statut
|
||||
addSuccessMessage("Utilisateur désactivé avec succès");
|
||||
} catch (Exception e) {
|
||||
@@ -194,6 +178,38 @@ public class UserProfilBean implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprimer l'utilisateur
|
||||
*/
|
||||
public void deleteUser() {
|
||||
try {
|
||||
userServiceClient.deleteUser(userId, realmName, false);
|
||||
addSuccessMessage("Utilisateur supprimé avec succès");
|
||||
FacesContext.getCurrentInstance().getExternalContext().redirect("list.xhtml");
|
||||
} catch (Exception e) {
|
||||
LOGGER.severe("Erreur lors de la suppression: " + e.getMessage());
|
||||
addErrorMessage("Erreur lors de la suppression: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Méthodes compatibles avec user-actions.xhtml (qui passe l'ID en paramètre)
|
||||
|
||||
public void activateUser(String userId) {
|
||||
activateUser();
|
||||
}
|
||||
|
||||
public void deactivateUser(String userId) {
|
||||
deactivateUser();
|
||||
}
|
||||
|
||||
public void resetPassword(String userId) {
|
||||
resetPassword();
|
||||
}
|
||||
|
||||
public void deleteUser(String userId) {
|
||||
deleteUser();
|
||||
}
|
||||
|
||||
/**
|
||||
* Déconnecter toutes les sessions
|
||||
*/
|
||||
@@ -207,15 +223,18 @@ public class UserProfilBean implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
public void logoutAllSessions(String userId) {
|
||||
logoutAllSessions();
|
||||
}
|
||||
|
||||
// Méthodes utilitaires
|
||||
private void addSuccessMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_INFO, "Succès", message));
|
||||
}
|
||||
|
||||
private void addErrorMessage(String message) {
|
||||
FacesContext.getCurrentInstance().addMessage(null,
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur", message));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package dev.lions.user.manager.client.view;
|
||||
|
||||
import dev.lions.user.manager.client.service.RealmAssignmentServiceClient;
|
||||
import io.quarkus.oidc.IdToken;
|
||||
import io.quarkus.oidc.OidcSession;
|
||||
import io.quarkus.security.identity.SecurityIdentity;
|
||||
@@ -12,11 +11,8 @@ import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import lombok.Data;
|
||||
import org.eclipse.microprofile.jwt.JsonWebToken;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
@@ -43,10 +39,6 @@ public class UserSessionBean implements Serializable {
|
||||
@Inject
|
||||
OidcSession oidcSession;
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
RealmAssignmentServiceClient realmAssignmentServiceClient;
|
||||
|
||||
// Informations utilisateur
|
||||
private String username;
|
||||
private String email;
|
||||
@@ -81,7 +73,7 @@ public class UserSessionBean implements Serializable {
|
||||
// Prénom et nom
|
||||
firstName = idToken.getClaim("given_name");
|
||||
lastName = idToken.getClaim("family_name");
|
||||
|
||||
|
||||
// Nom complet
|
||||
fullName = idToken.getClaim("name");
|
||||
if (fullName == null || fullName.trim().isEmpty()) {
|
||||
@@ -127,7 +119,7 @@ public class UserSessionBean implements Serializable {
|
||||
String[] parts = name.trim().split("\\s+");
|
||||
if (parts.length >= 2) {
|
||||
return String.valueOf(parts[0].charAt(0)).toUpperCase() +
|
||||
String.valueOf(parts[1].charAt(0)).toUpperCase();
|
||||
String.valueOf(parts[1].charAt(0)).toUpperCase();
|
||||
} else if (parts.length == 1) {
|
||||
String part = parts[0];
|
||||
if (part.length() >= 2) {
|
||||
@@ -178,7 +170,8 @@ public class UserSessionBean implements Serializable {
|
||||
*/
|
||||
private String getMainRole() {
|
||||
try {
|
||||
if (securityIdentity != null && securityIdentity.getRoles() != null && !securityIdentity.getRoles().isEmpty()) {
|
||||
if (securityIdentity != null && securityIdentity.getRoles() != null
|
||||
&& !securityIdentity.getRoles().isEmpty()) {
|
||||
// Prioriser certains rôles
|
||||
java.util.Set<String> roleSet = securityIdentity.getRoles();
|
||||
if (roleSet.contains("admin")) {
|
||||
@@ -218,90 +211,6 @@ public class UserSessionBean implements Serializable {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================== Gestion des realms autorisés ====================
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur est super admin (peut gérer tous les realms)
|
||||
*/
|
||||
public boolean isSuperAdmin() {
|
||||
try {
|
||||
if (getSubject() == null || "Non disponible".equals(getSubject())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RealmAssignmentServiceClient.AuthorizedRealmsResponse response =
|
||||
realmAssignmentServiceClient.getAuthorizedRealms(getSubject());
|
||||
|
||||
return response != null && response.isSuperAdmin;
|
||||
} catch (Exception e) {
|
||||
LOGGER.warning("Erreur lors de la vérification du statut super admin: " + e.getMessage());
|
||||
// En cas d'erreur réseau, vérifier le rôle local
|
||||
return hasRole("admin");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la liste des realms que l'utilisateur peut administrer
|
||||
* Retourne une liste vide si l'utilisateur est super admin (peut tout gérer)
|
||||
*/
|
||||
public List<String> getAuthorizedRealms() {
|
||||
try {
|
||||
if (getSubject() == null || "Non disponible".equals(getSubject())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
RealmAssignmentServiceClient.AuthorizedRealmsResponse response =
|
||||
realmAssignmentServiceClient.getAuthorizedRealms(getSubject());
|
||||
|
||||
if (response == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Si super admin, retourner liste vide (convention: peut tout gérer)
|
||||
if (response.isSuperAdmin) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return response.realms != null ? response.realms : Collections.emptyList();
|
||||
} catch (Exception e) {
|
||||
LOGGER.warning("Erreur lors de la récupération des realms autorisés: " + e.getMessage());
|
||||
// En cas d'erreur, si admin local, retourner liste vide (peut tout gérer)
|
||||
if (hasRole("admin")) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut administrer un realm spécifique
|
||||
*/
|
||||
public boolean canManageRealm(String realmName) {
|
||||
try {
|
||||
if (realmName == null || realmName.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getSubject() == null || "Non disponible".equals(getSubject())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Super admin peut tout gérer
|
||||
if (isSuperAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RealmAssignmentServiceClient.CheckResponse response =
|
||||
realmAssignmentServiceClient.canManageRealm(getSubject(), realmName);
|
||||
|
||||
return response != null && response.canManage;
|
||||
} catch (Exception e) {
|
||||
LOGGER.warning("Erreur lors de la vérification d'accès au realm " + realmName + ": " + e.getMessage());
|
||||
// En cas d'erreur réseau, vérifier le rôle local
|
||||
return hasRole("admin");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtenir l'issuer du token OIDC
|
||||
*/
|
||||
@@ -436,9 +345,10 @@ public class UserSessionBean implements Serializable {
|
||||
externalContext.invalidateSession();
|
||||
|
||||
// Rediriger vers l'endpoint de logout OIDC de Quarkus
|
||||
// Quarkus gère automatiquement la redirection vers Keycloak pour la déconnexion complète
|
||||
String logoutUrl = "/auth/logout";
|
||||
externalContext.redirect(logoutUrl);
|
||||
// Quarkus gère la déconnexion Keycloak (end_session_endpoint) + redirection
|
||||
// post-logout
|
||||
String contextPath = externalContext.getRequestContextPath();
|
||||
externalContext.redirect(contextPath + "/auth/logout");
|
||||
facesContext.responseComplete();
|
||||
|
||||
return null;
|
||||
@@ -449,4 +359,3 @@ public class UserSessionBean implements Serializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,20 +80,6 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Page de visualisation d'un utilisateur spécifique</description>
|
||||
<from-outcome>userViewPage</from-outcome>
|
||||
<to-view-id>/pages/user-manager/users/view.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers visualisation utilisateur</description>
|
||||
<from-outcome>/pages/user-manager/users/view</from-outcome>
|
||||
<to-view-id>/pages/user-manager/users/view.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Page d'édition utilisateur</description>
|
||||
<from-outcome>userEditPage</from-outcome>
|
||||
@@ -190,23 +176,6 @@
|
||||
<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>
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html 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"
|
||||
lang="fr">
|
||||
|
||||
<h:head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Lions User Manager - Gestion des Utilisateurs Keycloak</title>
|
||||
|
||||
<!-- PrimeFaces Freya Theme -->
|
||||
<h:outputStylesheet name="primefaces-freya/theme.css" />
|
||||
<h:outputStylesheet name="css/primeicons.css" library="freya-layout" />
|
||||
<h:outputStylesheet name="css/primeflex.min.css" library="freya-layout" />
|
||||
</h:head>
|
||||
|
||||
<h:body>
|
||||
<div class="flex align-items-center justify-content-center" style="min-height: 100vh; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||
<div class="card" style="width: 90%; max-width: 600px; text-align: center;">
|
||||
<div class="flex flex-column align-items-center gap-3 p-5">
|
||||
<i class="pi pi-users text-6xl text-primary"></i>
|
||||
<h1 class="text-4xl font-bold m-0">Lions User Manager</h1>
|
||||
<p class="text-xl text-600 m-0">Gestion centralisée des utilisateurs Keycloak</p>
|
||||
|
||||
<div class="flex flex-column gap-2 mt-4" style="width: 100%;">
|
||||
<h:link outcome="/pages/user-manager/users/list" styleClass="no-underline">
|
||||
<p:commandButton value="Accéder à la Gestion des Utilisateurs"
|
||||
icon="pi pi-users"
|
||||
styleClass="w-full p-button-lg" />
|
||||
</h:link>
|
||||
|
||||
<h:link outcome="/pages/user-manager/roles/list" styleClass="no-underline">
|
||||
<p:commandButton value="Gestion des Rôles"
|
||||
icon="pi pi-shield"
|
||||
styleClass="w-full p-button-lg p-button-outlined" />
|
||||
</h:link>
|
||||
|
||||
<h:link outcome="/pages/user-manager/audit/logs" styleClass="no-underline">
|
||||
<p:commandButton value="Journal d'Audit"
|
||||
icon="pi pi-history"
|
||||
styleClass="w-full p-button-lg p-button-outlined" />
|
||||
</h:link>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-600">
|
||||
<p class="m-0">Version 1.0.0</p>
|
||||
<p class="m-0 text-sm">Module réutilisable pour l'écosystème LionsDev</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,316 +1,203 @@
|
||||
<!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">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{auditConsultationBean}"/>
|
||||
<ui:param name="page" value="#{auditConsultationBean}" />
|
||||
<ui:define name="title">Journal d'Audit - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<!-- ================================================================
|
||||
EN-TÊTE DE LA PAGE
|
||||
================================================================ -->
|
||||
<!-- En-tête -->
|
||||
<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-history text-orange-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Journal d'Audit</h3>
|
||||
<p class="text-600 m-0">Consultation des logs d'audit et statistiques système</p>
|
||||
<ui:decorate template="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-history text-orange-500" />
|
||||
<ui:param name="title" value="Journal d'Audit" />
|
||||
<ui:param name="description" value="Consultation des logs d'audit et statistiques" />
|
||||
<ui:define name="actions">
|
||||
<h:form id="formActionsAudit">
|
||||
<div class="flex gap-2">
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Exporter CSV" />
|
||||
<ui:param name="icon" value="pi pi-download" />
|
||||
<ui:param name="hasAction" value="true" />
|
||||
<ui:param name="action" value="#{auditConsultationBean.exportToCSV}" />
|
||||
<ui:param name="severity" value="success" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
<h:form id="formHeaderActions">
|
||||
<fr:commandButton value="Exporter CSV"
|
||||
icon="pi pi-download"
|
||||
severity="success"
|
||||
action="#{auditConsultationBean.exportToCSV}"
|
||||
ajax="false" />
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
STATISTIQUES KPI (4 CARTES)
|
||||
================================================================ -->
|
||||
<!-- Statistiques avec composants réutilisables -->
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3">Statistiques d'Audit</h5>
|
||||
<ui:decorate template="/templates/components/shared/dashboard/kpi-group.xhtml">
|
||||
<ui:param name="title" value="Statistiques d'Audit" />
|
||||
<ui:param name="columns" value="4" />
|
||||
<ui:define name="kpi-content">
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Actions" />
|
||||
<ui:param name="value" value="#{auditConsultationBean.totalRecords}" />
|
||||
<ui:param name="icon" value="pi-history" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Actions Réussies" />
|
||||
<ui:param name="value" value="#{auditConsultationBean.successCount}" />
|
||||
<ui:param name="icon" value="pi-check-circle" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Actions Échouées" />
|
||||
<ui:param name="value" value="#{auditConsultationBean.failureCount}" />
|
||||
<ui:param name="icon" value="pi-times-circle" />
|
||||
<ui:param name="iconColor" value="red-600" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Taux de Réussite" />
|
||||
<ui:param name="value"
|
||||
value="#{auditConsultationBean.totalRecords > 0 ? (auditConsultationBean.successCount * 100 / auditConsultationBean.totalRecords) : 0}%" />
|
||||
<ui:param name="icon" value="pi-percentage" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
</ui:include>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
|
||||
<!-- KPI 1: Total Actions -->
|
||||
<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 Actions</div>
|
||||
<div class="text-900 font-bold text-2xl">#{auditConsultationBean.totalRecords}</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-history text-blue-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-database text-600"></i>
|
||||
<span class="ml-2">Actions enregistrées</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI 2: Actions Réussies -->
|
||||
<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">Actions Réussies</div>
|
||||
<div class="text-900 font-bold text-2xl">#{auditConsultationBean.successCount}</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="flex align-items-center gap-2">
|
||||
<span class="text-green-600 font-semibold">
|
||||
<i class="pi pi-check text-xs"></i>
|
||||
Succès
|
||||
</span>
|
||||
<span class="text-500 text-sm">Opérations validées</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI 3: Actions Échouées -->
|
||||
<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">Actions Échouées</div>
|
||||
<div class="text-900 font-bold text-2xl">#{auditConsultationBean.failureCount}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-red-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-times-circle text-red-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<span class="text-red-600 font-semibold">
|
||||
<i class="pi pi-times text-xs"></i>
|
||||
Échecs
|
||||
</span>
|
||||
<span class="text-500 text-sm">Opérations en erreur</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI 4: Taux de Réussite -->
|
||||
<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">Taux de Réussite</div>
|
||||
<div class="text-900 font-bold text-2xl">
|
||||
#{auditConsultationBean.totalRecords > 0 ? (auditConsultationBean.successCount * 100 / auditConsultationBean.totalRecords) : 0}%
|
||||
</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-percentage text-purple-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-chart-line text-600"></i>
|
||||
<span class="ml-2">Performance globale</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
FILTRES DE RECHERCHE
|
||||
================================================================ -->
|
||||
<!-- Filtres de recherche -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-filter text-blue-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Filtres de Recherche</h5>
|
||||
</div>
|
||||
|
||||
<h:form id="formFilters">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<fr:fieldInput id="acteurFilter"
|
||||
label="Acteur"
|
||||
value="#{auditConsultationBean.acteurUsername}"
|
||||
placeholder="Nom d'utilisateur..."
|
||||
iconLeft="pi pi-user" />
|
||||
<h5 class="mb-3">Filtres de recherche</h5>
|
||||
<div class="grid ui-fluid">
|
||||
<div class="col-12 md:col-4 field">
|
||||
<p:outputLabel for="acteurFilter" value="Acteur" />
|
||||
<p:inputText id="acteurFilter" value="#{auditConsultationBean.acteurUsername}"
|
||||
placeholder="Nom d'utilisateur..." />
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<fr:fieldSelect id="typeActionFilter"
|
||||
label="Type d'action"
|
||||
value="#{auditConsultationBean.selectedTypeAction}"
|
||||
iconLeft="pi pi-bolt">
|
||||
<div class="col-12 md:col-4 field">
|
||||
<p:outputLabel for="typeActionFilter" value="Type d'action" />
|
||||
<p:selectOneMenu id="typeActionFilter"
|
||||
value="#{auditConsultationBean.selectedTypeAction}">
|
||||
<f:selectItem itemLabel="Tous les types" itemValue="" />
|
||||
<f:selectItems value="#{auditConsultationBean.typeActionOptions}" />
|
||||
</fr:fieldSelect>
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<fr:fieldSelect id="succesFilter"
|
||||
label="Résultat"
|
||||
value="#{auditConsultationBean.succes}"
|
||||
iconLeft="pi pi-check-circle">
|
||||
<div class="col-12 md:col-4 field">
|
||||
<p:outputLabel for="succesFilter" value="Résultat" />
|
||||
<p:selectOneMenu id="succesFilter" value="#{auditConsultationBean.succes}">
|
||||
<f:selectItem itemLabel="Tous" itemValue="" />
|
||||
<f:selectItem itemLabel="Succès" itemValue="true" />
|
||||
<f:selectItem itemLabel="Échec" itemValue="false" />
|
||||
</fr:fieldSelect>
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<fr:fieldCalendar id="dateDebutFilter"
|
||||
label="Date début"
|
||||
value="#{auditConsultationBean.dateDebut}"
|
||||
pattern="dd/MM/yyyy"
|
||||
showIcon="true" />
|
||||
<div class="col-12 md:col-4 field">
|
||||
<p:outputLabel for="dateDebutFilter" value="Date début" />
|
||||
<p:calendar id="dateDebutFilter" value="#{auditConsultationBean.dateDebut}"
|
||||
pattern="dd/MM/yyyy" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<fr:fieldCalendar id="dateFinFilter"
|
||||
label="Date fin"
|
||||
value="#{auditConsultationBean.dateFin}"
|
||||
pattern="dd/MM/yyyy"
|
||||
showIcon="true" />
|
||||
<div class="col-12 md:col-4 field">
|
||||
<p:outputLabel for="dateFinFilter" value="Date fin" />
|
||||
<p:calendar id="dateFinFilter" value="#{auditConsultationBean.dateFin}"
|
||||
pattern="dd/MM/yyyy" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<fr:fieldInput id="ressourceFilter"
|
||||
label="Type ressource"
|
||||
value="#{auditConsultationBean.ressourceType}"
|
||||
placeholder="USER, ROLE, CLIENT..."
|
||||
iconLeft="pi pi-database" />
|
||||
<div class="col-12 md:col-4 field">
|
||||
<p:outputLabel for="ressourceFilter" value="Type ressource" />
|
||||
<p:inputText id="ressourceFilter" value="#{auditConsultationBean.ressourceType}"
|
||||
placeholder="USER, ROLE..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 justify-content-end mt-4">
|
||||
<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 class="flex gap-2 justify-content-end mt-3">
|
||||
<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" />
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
TABLEAU DES LOGS D'AUDIT
|
||||
================================================================ -->
|
||||
<!-- Liste des logs avec p:dataTable -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-list text-blue-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Logs d'Audit</h5>
|
||||
</div>
|
||||
<fr:tag value="#{auditConsultationBean.totalRecords} log(s)"
|
||||
severity="info"
|
||||
icon="pi pi-history" />
|
||||
</div>
|
||||
|
||||
<h:form id="formAuditLogs">
|
||||
<p:dataTable
|
||||
id="auditLogsTable"
|
||||
value="#{auditConsultationBean.auditLogs}"
|
||||
var="log"
|
||||
rowKey="#{log.id}"
|
||||
paginator="true"
|
||||
rows="20"
|
||||
rowsPerPageTemplate="10,20,50,100"
|
||||
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
|
||||
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
|
||||
styleClass="w-full"
|
||||
emptyMessage="Aucun log d'audit trouvé"
|
||||
responsiveLayout="scroll">
|
||||
<h5>Logs d'Audit</h5>
|
||||
<p:dataTable id="auditLogsTable" value="#{auditConsultationBean.auditLogs}" var="log"
|
||||
rowKey="#{log.id}" paginator="true" rows="20" rowsPerPageTemplate="10,20,50,100"
|
||||
emptyMessage="Aucun log d'audit trouvé" styleClass="w-full">
|
||||
|
||||
<!-- Colonne Statut -->
|
||||
<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 headerText="Statut" style="width: 5rem">
|
||||
<p:tag value="#{log.succes ? 'Succès' : 'Échec'}"
|
||||
severity="#{log.succes ? 'success' : 'danger'}" styleClass="text-xs" />
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Type d'action -->
|
||||
<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>
|
||||
</div>
|
||||
<p:column headerText="Type d'action" sortBy="#{log.typeAction}" style="width: 15%">
|
||||
<strong class="text-900">#{log.typeAction}</strong>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Acteur -->
|
||||
<p:column headerText="Acteur" sortBy="#{log.acteurUsername}" style="width: 200px" priority="3">
|
||||
<p:column headerText="Acteur" sortBy="#{log.acteurUsername}" style="width: 15%">
|
||||
<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;">
|
||||
<span class="font-bold">
|
||||
#{log.acteurUsername != null and log.acteurUsername.length() > 1 ? log.acteurUsername.substring(0,2).toUpperCase() : 'XX'}
|
||||
</span>
|
||||
</div>
|
||||
<span class="text-900">#{log.acteurUsername}</span>
|
||||
<i class="pi pi-user text-color-secondary"></i>
|
||||
<span>#{log.acteurUsername}</span>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Ressource -->
|
||||
<p:column headerText="Ressource" style="width: 150px" priority="5">
|
||||
<p:column headerText="Ressource" style="width: 12%">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-database text-blue-500"></i>
|
||||
<span class="text-900">#{log.ressourceType}</span>
|
||||
<i class="pi pi-database text-color-secondary"></i>
|
||||
<span>#{log.ressourceType}</span>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Date -->
|
||||
<p:column headerText="Date" sortBy="#{log.dateAction}" style="width: 180px" priority="4">
|
||||
<p:column headerText="Date" sortBy="#{log.dateAction}" style="width: 15%">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-calendar text-purple-500"></i>
|
||||
<span class="text-900">#{log.dateAction}</span>
|
||||
<i class="pi pi-calendar text-color-secondary"></i>
|
||||
<span>#{log.dateAction}</span>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Détails -->
|
||||
<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 headerText="Détails" style="width: 20%">
|
||||
<c:if test="#{not empty log.details}">
|
||||
<span class="text-color-secondary text-sm">#{log.details}</span>
|
||||
</c:if>
|
||||
<c:if test="#{empty log.details}">
|
||||
<span class="text-color-secondary text-sm">-</span>
|
||||
</c:if>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne IP -->
|
||||
<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 headerText="IP" style="width: 10%">
|
||||
<c:if test="#{not empty log.adresseIp}">
|
||||
<span class="text-color-secondary text-sm">#{log.adresseIp}</span>
|
||||
</c:if>
|
||||
<c:if test="#{empty log.adresseIp}">
|
||||
<span class="text-color-secondary text-sm">-</span>
|
||||
</c:if>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Actions -->
|
||||
<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}" />
|
||||
</fr:commandButton>
|
||||
<p:column headerText="Actions" style="width: 8%">
|
||||
<p:commandButton icon="pi pi-eye" styleClass="p-button-text p-button-sm"
|
||||
title="Voir les détails" update=":formAuditLogDetails"
|
||||
oncomplete="PF('auditLogDetailsDialog').show()">
|
||||
<f:setPropertyActionListener target="#{auditConsultationBean.selectedLog}"
|
||||
value="#{log}" />
|
||||
</p:commandButton>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</h:form>
|
||||
@@ -318,103 +205,57 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE DÉTAILS DU LOG
|
||||
================================================================ -->
|
||||
<p:dialog
|
||||
id="auditLogDetailsDialog"
|
||||
widgetVar="auditLogDetailsDialog"
|
||||
header="Détails du Log d'Audit"
|
||||
modal="true"
|
||||
resizable="false"
|
||||
styleClass="w-full md:w-40rem">
|
||||
<!-- Dialog de détails -->
|
||||
<p:dialog id="auditLogDetailsDialog" widgetVar="auditLogDetailsDialog" header="Détails du Log d'Audit"
|
||||
modal="true" resizable="false" styleClass="w-full md:w-30rem">
|
||||
<h:form id="formAuditLogDetails">
|
||||
<div class="flex flex-column gap-3">
|
||||
<!-- Statut -->
|
||||
<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>
|
||||
<fr:tag value="#{auditConsultationBean.selectedLog.succes ? 'Succès' : 'Échec'}"
|
||||
severity="#{auditConsultationBean.selectedLog.succes ? 'success' : 'danger'}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Type d'action -->
|
||||
<div>
|
||||
<label class="block text-600 font-medium mb-2">Type d'action</label>
|
||||
<p class="text-900 font-semibold m-0">#{auditConsultationBean.selectedLog.typeAction}</p>
|
||||
</div>
|
||||
|
||||
<!-- Acteur -->
|
||||
<div>
|
||||
<label class="block text-600 font-medium mb-2">Acteur</label>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-user text-500"></i>
|
||||
<p class="text-900 m-0">#{auditConsultationBean.selectedLog.acteurUsername}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ressource -->
|
||||
<div>
|
||||
<label class="block text-600 font-medium mb-2">Ressource</label>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-database text-500"></i>
|
||||
<p class="text-900 m-0">#{auditConsultationBean.selectedLog.ressourceType}</p>
|
||||
</div>
|
||||
<small class="text-500">ID: #{auditConsultationBean.selectedLog.ressourceId}</small>
|
||||
</div>
|
||||
|
||||
<!-- Date -->
|
||||
<div>
|
||||
<label class="block text-600 font-medium mb-2">Date</label>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-calendar text-500"></i>
|
||||
<p class="text-900 m-0">#{auditConsultationBean.selectedLog.dateAction}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Détails -->
|
||||
<h:panelGroup rendered="#{not empty auditConsultationBean.selectedLog.details}">
|
||||
<c:if test="#{not empty auditConsultationBean.selectedLog}">
|
||||
<div class="flex flex-column gap-3">
|
||||
<div>
|
||||
<label class="block text-600 font-medium mb-2">Détails</label>
|
||||
<p class="text-900 m-0 white-space-pre-wrap">#{auditConsultationBean.selectedLog.details}</p>
|
||||
<strong>Type d'action:</strong>
|
||||
<p>#{auditConsultationBean.selectedLog.typeAction}</p>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
<!-- Adresse IP -->
|
||||
<h:panelGroup rendered="#{not empty auditConsultationBean.selectedLog.adresseIp}">
|
||||
<div>
|
||||
<label class="block text-600 font-medium mb-2">Adresse IP</label>
|
||||
<p class="text-900 m-0 font-mono">#{auditConsultationBean.selectedLog.adresseIp}</p>
|
||||
<strong>Acteur:</strong>
|
||||
<p>#{auditConsultationBean.selectedLog.acteurUsername}</p>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
<!-- User Agent -->
|
||||
<h:panelGroup rendered="#{not empty auditConsultationBean.selectedLog.userAgent}">
|
||||
<div>
|
||||
<label class="block text-600 font-medium mb-2">User Agent</label>
|
||||
<p class="text-600 text-sm m-0 white-space-pre-wrap">#{auditConsultationBean.selectedLog.userAgent}</p>
|
||||
<strong>Ressource:</strong>
|
||||
<p>#{auditConsultationBean.selectedLog.ressourceType} -
|
||||
#{auditConsultationBean.selectedLog.ressourceId}</p>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
<!-- Message d'erreur -->
|
||||
<h:panelGroup rendered="#{not empty auditConsultationBean.selectedLog.messageErreur}">
|
||||
<div class="surface-red-50 border-round p-3">
|
||||
<label class="block text-red-600 font-medium mb-2">Message d'erreur</label>
|
||||
<p class="text-red-600 m-0 white-space-pre-wrap">#{auditConsultationBean.selectedLog.messageErreur}</p>
|
||||
<div>
|
||||
<strong>Date:</strong>
|
||||
<p>#{auditConsultationBean.selectedLog.dateAction}</p>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-content-end mt-4">
|
||||
<fr:commandButton value="Fermer"
|
||||
icon="pi pi-times"
|
||||
severity="secondary"
|
||||
onclick="PF('auditLogDetailsDialog').hide()"
|
||||
type="button" />
|
||||
</div>
|
||||
<c:if test="#{not empty auditConsultationBean.selectedLog.details}">
|
||||
<div>
|
||||
<strong>Détails:</strong>
|
||||
<p>#{auditConsultationBean.selectedLog.details}</p>
|
||||
</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditConsultationBean.selectedLog.adresseIp}">
|
||||
<div>
|
||||
<strong>Adresse IP:</strong>
|
||||
<p>#{auditConsultationBean.selectedLog.adresseIp}</p>
|
||||
</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditConsultationBean.selectedLog.userAgent}">
|
||||
<div>
|
||||
<strong>User Agent:</strong>
|
||||
<p>#{auditConsultationBean.selectedLog.userAgent}</p>
|
||||
</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditConsultationBean.selectedLog.messageErreur}">
|
||||
<div>
|
||||
<strong class="text-red-600">Message d'erreur:</strong>
|
||||
<p class="text-red-600">#{auditConsultationBean.selectedLog.messageErreur}</p>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:if>
|
||||
</h:form>
|
||||
</p:dialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
</ui:composition>
|
||||
@@ -1,406 +1,148 @@
|
||||
<!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">
|
||||
<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" template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:define name="title">Tableau de Bord - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<h:form id="formDashboard">
|
||||
<p:outputPanel id="dashboardPanel">
|
||||
<div class="grid">
|
||||
<!-- ================================================================
|
||||
EN-TÊTE DE LA PAGE
|
||||
================================================================ -->
|
||||
<!-- En-tête -->
|
||||
<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-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 - Realm: #{dashboardBean.realmName}</p>
|
||||
</div>
|
||||
</div>
|
||||
<fr:commandButton value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
severity="secondary"
|
||||
action="#{dashboardBean.refreshStatistics}"
|
||||
update=":formDashboard" />
|
||||
</div>
|
||||
</div>
|
||||
<ui:decorate template="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-home text-blue-500" />
|
||||
<ui:param name="title" value="Tableau de Bord" />
|
||||
<ui:param name="description" value="Vue d'ensemble de la gestion des utilisateurs Keycloak" />
|
||||
<ui:define name="actions">
|
||||
<h:form id="formRefresh">
|
||||
<p:commandButton value="Rafraîchir" icon="pi pi-refresh" styleClass="p-button-secondary"
|
||||
action="#{dashboardBean.refreshStatistics}" update=":dashboardPanel" />
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
STATISTIQUES PRINCIPALES - KPIs MÉTIER
|
||||
================================================================ -->
|
||||
<!-- KPIs Principaux avec composant réutilisable -->
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3">Indicateurs Clés de Performance</h5>
|
||||
<ui:decorate template="/templates/components/shared/dashboard/kpi-group.xhtml">
|
||||
<ui:param name="title" value="Statistiques Principales" />
|
||||
<ui:param name="columns" value="4" />
|
||||
<ui:define name="kpi-content">
|
||||
<!-- KPI 1: Utilisateurs Actifs -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Utilisateurs Actifs" />
|
||||
<ui:param name="value" value="#{dashboardBean.totalUsersDisplay}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="subtitle" value="Total utilisateurs" />
|
||||
<ui:param name="clickable" value="true" />
|
||||
<ui:param name="clickOutcome" value="/pages/user-manager/users/list" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 2: Rôles Realm -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Rôles Realm" />
|
||||
<ui:param name="value" value="#{dashboardBean.totalRolesDisplay}" />
|
||||
<ui:param name="icon" value="pi-shield" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="subtitle" value="Rôles configurés" />
|
||||
<ui:param name="clickable" value="true" />
|
||||
<ui:param name="clickOutcome" value="/pages/user-manager/roles/list" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 3: Actions Récentes -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Actions Récentes" />
|
||||
<ui:param name="value" value="#{dashboardBean.recentActionsDisplay}" />
|
||||
<ui:param name="icon" value="pi-history" />
|
||||
<ui:param name="iconColor" value="orange-600" />
|
||||
<ui:param name="subtitle" value="Dernières 24h" />
|
||||
<ui:param name="clickable" value="true" />
|
||||
<ui:param name="clickOutcome" value="/pages/user-manager/audit/logs" />
|
||||
</ui:include>
|
||||
|
||||
<!-- KPI 4: Rôles Client -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Rôles Client" />
|
||||
<ui:param name="value" value="-" />
|
||||
<ui:param name="icon" value="pi-key" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="subtitle" value="Rôles clients configurés" />
|
||||
<ui:param name="clickable" value="true" />
|
||||
<ui:param name="clickOutcome" value="/pages/user-manager/roles/list" />
|
||||
</ui:include>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<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-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: 3.5rem; height: 3.5rem">
|
||||
<i class="pi pi-users text-blue-600" style="font-size: 1.75rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
</fr:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<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-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: 3.5rem; height: 3.5rem">
|
||||
<i class="pi pi-check-circle text-green-600" style="font-size: 1.75rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
</fr:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<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-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: 3.5rem; height: 3.5rem">
|
||||
<i class="pi pi-ban text-orange-600" style="font-size: 1.75rem"></i>
|
||||
</div>
|
||||
</div>
|
||||
</fr:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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 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="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>
|
||||
|
||||
<!-- ================================================================
|
||||
ACTIONS RAPIDES
|
||||
================================================================ -->
|
||||
<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-bolt text-orange-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Actions Rapides</h5>
|
||||
</div>
|
||||
|
||||
<!-- Actions Rapides -->
|
||||
<ui:decorate template="/templates/components/shared/dashboard/dashboard-section.xhtml">
|
||||
<ui:param name="title" value="Actions Rapides" />
|
||||
<ui:param name="icon" value="pi-bolt" />
|
||||
<ui:param name="colSize" value="col-12 lg:col-6" />
|
||||
<ui:define name="section-content">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:commandButton value="Nouvel Utilisateur"
|
||||
icon="pi pi-user-plus"
|
||||
severity="success"
|
||||
styleClass="w-full mb-2"
|
||||
outcome="/pages/user-manager/users/create" />
|
||||
<p:button value="Nouvel Utilisateur" icon="pi pi-user-plus"
|
||||
styleClass="w-full p-button-success" outcome="/pages/user-manager/users/create" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:commandButton value="Liste des Utilisateurs"
|
||||
icon="pi pi-users"
|
||||
severity="primary"
|
||||
styleClass="w-full mb-2"
|
||||
outcome="/pages/user-manager/users/list" />
|
||||
<p:button value="Liste des Utilisateurs" icon="pi pi-users"
|
||||
styleClass="w-full p-button-primary" outcome="/pages/user-manager/users/list" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:commandButton value="Gestion des Rôles"
|
||||
icon="pi pi-shield"
|
||||
severity="info"
|
||||
styleClass="w-full mb-2"
|
||||
outcome="/pages/user-manager/roles/list" />
|
||||
<p:button value="Gestion des Rôles" icon="pi pi-shield"
|
||||
styleClass="w-full p-button-info" outcome="/pages/user-manager/roles/list" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:commandButton value="Journal d'Audit"
|
||||
icon="pi pi-history"
|
||||
severity="help"
|
||||
styleClass="w-full mb-2"
|
||||
outcome="/pages/user-manager/audit/logs" />
|
||||
<p:button value="Journal d'Audit" icon="pi pi-history" styleClass="w-full p-button-help"
|
||||
outcome="/pages/user-manager/audit/logs" />
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
|
||||
<div class="mt-3 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">Conseil</div>
|
||||
<small class="text-600">Utilisez ces raccourcis pour accéder rapidement aux fonctionnalités principales</small>
|
||||
</div>
|
||||
<!-- Informations Système -->
|
||||
<ui:decorate template="/templates/components/shared/dashboard/dashboard-section.xhtml">
|
||||
<ui:param name="title" value="Informations Système" />
|
||||
<ui:param name="icon" value="pi-info-circle" />
|
||||
<ui:param name="colSize" value="col-12 lg:col-6" />
|
||||
<ui:define name="section-content">
|
||||
<div class="flex flex-column gap-2">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Version</span>
|
||||
<span class="font-semibold">1.0.0</span>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Realm Keycloak</span>
|
||||
<span class="font-semibold">lions-user-manager</span>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Statut</span>
|
||||
<p:tag value="Opérationnel" severity="success" />
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Application</span>
|
||||
<span class="font-semibold">Lions User Manager</span>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Environnement</span>
|
||||
<span class="font-semibold">Développement</span>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Base de données</span>
|
||||
<span class="font-semibold">Keycloak Admin API</span>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Framework</span>
|
||||
<span class="font-semibold">Quarkus, PrimeFaces Freya</span>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
INFORMATIONS SYSTÈME
|
||||
================================================================ -->
|
||||
<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-cyan-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Informations Système</h5>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column gap-3">
|
||||
<!-- Version -->
|
||||
<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-tag text-500"></i>
|
||||
<span class="text-600 font-medium">Version Application</span>
|
||||
</div>
|
||||
<span class="font-semibold text-900">1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Framework -->
|
||||
<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-code text-500"></i>
|
||||
<span class="text-600 font-medium">Framework</span>
|
||||
</div>
|
||||
<span class="font-semibold text-900 text-right">Quarkus + PrimeFaces Freya</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 Système</span>
|
||||
</div>
|
||||
<fr:tag value="Opérationnel" severity="success" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
</h:form>
|
||||
</p:outputPanel>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,918 @@
|
||||
<!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">
|
||||
|
||||
<ui:define name="title">Freya Extension Showcase - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<h:form id="formFreyaShowcase">
|
||||
<div class="grid">
|
||||
<!-- ================================================================
|
||||
EN-TÊTE
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex align-items-center gap-3">
|
||||
<i class="pi pi-palette text-purple-500" style="font-size: 3rem"></i>
|
||||
<div>
|
||||
<h1 class="m-0 mb-2">PrimeFaces Freya Extension Showcase</h1>
|
||||
<p class="text-600 m-0 mb-1">Démonstration complète des 46 composants personnalisés</p>
|
||||
<div class="flex gap-2 align-items-center">
|
||||
<fr:tag value="46 Composants" severity="success" icon="pi pi-check" />
|
||||
<fr:tag value="Version 1.0.0-SNAPSHOT" severity="info" />
|
||||
<fr:tag value="Intégration Lions User Manager" severity="warning" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<fr:commandButton
|
||||
value="Retour Dashboard"
|
||||
icon="pi pi-home"
|
||||
severity="secondary"
|
||||
outcome="/pages/user-manager/dashboard" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION: COMPOSANTS DE FORMULAIRE (FIELD PATTERN) - 20 composants
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<h2 class="section-title flex align-items-center gap-2">
|
||||
<i class="pi pi-inbox text-blue-500"></i>
|
||||
Composants de Formulaire (Field Pattern)
|
||||
<fr:tag value="20 composants" severity="info" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- fieldInput -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldInput</h5>
|
||||
<p class="text-600 mb-3">Champ de saisie texte avec label et message de validation</p>
|
||||
|
||||
<fr:fieldInput id="nomUtilisateur"
|
||||
label="Nom complet"
|
||||
value="#{demoBean.user.nom}"
|
||||
required="true"
|
||||
placeholder="Ex: Jean Dupont" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldPassword -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldPassword</h5>
|
||||
<p class="text-600 mb-3">Champ mot de passe avec validation de force</p>
|
||||
|
||||
<fr:fieldPassword id="motDePasse"
|
||||
label="Mot de passe"
|
||||
value="#{demoBean.user.password}"
|
||||
required="true"
|
||||
feedback="true"
|
||||
promptLabel="Entrez un mot de passe"
|
||||
weakLabel="Faible"
|
||||
goodLabel="Moyen"
|
||||
strongLabel="Fort" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldTextarea -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldTextarea</h5>
|
||||
<p class="text-600 mb-3">Zone de texte multiligne pour descriptions</p>
|
||||
|
||||
<fr:fieldTextarea id="description"
|
||||
label="Description"
|
||||
value="#{demoBean.user.description}"
|
||||
rows="4"
|
||||
maxlength="500"
|
||||
placeholder="Entrez une description détaillée..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldNumber -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldNumber</h5>
|
||||
<p class="text-600 mb-3">Champ numérique avec contrôles + / -</p>
|
||||
|
||||
<fr:fieldNumber id="age"
|
||||
label="Âge"
|
||||
value="#{demoBean.user.age}"
|
||||
minValue="0"
|
||||
maxValue="150"
|
||||
showButtons="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldCalendar -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldCalendar</h5>
|
||||
<p class="text-600 mb-3">Sélecteur de date avec calendrier popup</p>
|
||||
|
||||
<fr:fieldCalendar id="dateNaissance"
|
||||
label="Date de naissance"
|
||||
value="#{demoBean.user.dateNaissance}"
|
||||
showIcon="true"
|
||||
yearRange="1900:2024"
|
||||
navigator="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldSelect -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldSelect</h5>
|
||||
<p class="text-600 mb-3">Liste déroulante simple sélection</p>
|
||||
|
||||
<fr:fieldSelect id="pays"
|
||||
label="Pays"
|
||||
value="#{demoBean.user.pays}">
|
||||
<f:selectItem itemLabel="Sélectionnez un pays" itemValue="" />
|
||||
<f:selectItem itemLabel="France" itemValue="FR" />
|
||||
<f:selectItem itemLabel="Belgique" itemValue="BE" />
|
||||
<f:selectItem itemLabel="Canada" itemValue="CA" />
|
||||
<f:selectItem itemLabel="Suisse" itemValue="CH" />
|
||||
</fr:fieldSelect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldMultiSelect -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldMultiSelect</h5>
|
||||
<p class="text-600 mb-3">Sélection multiple avec chips</p>
|
||||
|
||||
<fr:fieldMultiSelect id="competences"
|
||||
label="Compétences"
|
||||
value="#{demoBean.user.competences}">
|
||||
<f:selectItem itemLabel="Java" itemValue="java" />
|
||||
<f:selectItem itemLabel="JavaScript" itemValue="js" />
|
||||
<f:selectItem itemLabel="Python" itemValue="python" />
|
||||
<f:selectItem itemLabel="React" itemValue="react" />
|
||||
<f:selectItem itemLabel="Angular" itemValue="angular" />
|
||||
</fr:fieldMultiSelect>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldCheckbox -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldCheckbox</h5>
|
||||
<p class="text-600 mb-3">Case à cocher pour valeurs booléennes</p>
|
||||
|
||||
<fr:fieldCheckbox id="actif"
|
||||
label="Utilisateur actif"
|
||||
value="#{demoBean.user.actif}"
|
||||
checkboxLabel="Compte activé" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldRadio -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldRadio</h5>
|
||||
<p class="text-600 mb-3">Boutons radio pour choix exclusif</p>
|
||||
|
||||
<fr:fieldRadio id="genre"
|
||||
label="Genre"
|
||||
value="#{demoBean.user.genre}"
|
||||
layout="grid">
|
||||
<f:selectItem itemLabel="Homme" itemValue="M" />
|
||||
<f:selectItem itemLabel="Femme" itemValue="F" />
|
||||
<f:selectItem itemLabel="Autre" itemValue="O" />
|
||||
</fr:fieldRadio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldSwitch -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldSwitch</h5>
|
||||
<p class="text-600 mb-3">Interrupteur on/off moderne</p>
|
||||
|
||||
<fr:fieldSwitch id="newsletter"
|
||||
label="Notifications"
|
||||
value="#{demoBean.user.newsletter}"
|
||||
switchLabel="Recevoir la newsletter" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldToggle -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldToggle</h5>
|
||||
<p class="text-600 mb-3">Bouton toggle avec états on/off</p>
|
||||
|
||||
<fr:fieldToggle id="modeNuit"
|
||||
label="Préférences d'affichage"
|
||||
value="#{demoBean.user.modeNuit}"
|
||||
onLabel="Mode Nuit"
|
||||
offLabel="Mode Jour"
|
||||
onIcon="pi pi-moon"
|
||||
offIcon="pi pi-sun" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldSlider -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldSlider</h5>
|
||||
<p class="text-600 mb-3">Curseur pour sélection de valeur numérique</p>
|
||||
|
||||
<fr:fieldSlider id="volume"
|
||||
label="Volume"
|
||||
value="#{demoBean.user.volume}"
|
||||
minValue="0"
|
||||
maxValue="100"
|
||||
step="5" />
|
||||
<p class="text-600 text-sm mt-2">Valeur sélectionnée: #{demoBean.user.volume != null ? demoBean.user.volume : 50}%</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldRating -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldRating</h5>
|
||||
<p class="text-600 mb-3">Évaluation par étoiles</p>
|
||||
|
||||
<fr:fieldRating id="satisfaction"
|
||||
label="Satisfaction"
|
||||
value="#{demoBean.user.rating}"
|
||||
stars="5"
|
||||
cancel="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldChips -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldChips</h5>
|
||||
<p class="text-600 mb-3">Saisie de tags/mots-clés multiples</p>
|
||||
|
||||
<fr:fieldChips id="tags"
|
||||
label="Tags"
|
||||
value="#{demoBean.user.tags}"
|
||||
placeholder="Entrez un tag et appuyez sur Entrée" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldColor -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldColor</h5>
|
||||
<p class="text-600 mb-3">Sélecteur de couleur avec palette</p>
|
||||
|
||||
<fr:fieldColor id="couleurPref"
|
||||
label="Couleur préférée"
|
||||
value="#{demoBean.user.couleur}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldEditor -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldEditor</h5>
|
||||
<p class="text-600 mb-3">Éditeur de texte riche WYSIWYG</p>
|
||||
|
||||
<fr:fieldEditor id="bio"
|
||||
label="Biographie"
|
||||
value="#{demoBean.user.bio}"
|
||||
height="200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldMask -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldMask</h5>
|
||||
<p class="text-600 mb-3">Champ avec masque de saisie (téléphone, etc.)</p>
|
||||
|
||||
<fr:fieldMask id="telephone"
|
||||
label="Téléphone"
|
||||
value="#{demoBean.user.telephone}"
|
||||
mask="(999) 999-9999"
|
||||
placeholder="(555) 123-4567" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldAutoComplete -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldAutoComplete</h5>
|
||||
<p class="text-600 mb-3">Saisie avec suggestions automatiques</p>
|
||||
|
||||
<fr:fieldAutoComplete id="ville"
|
||||
label="Ville"
|
||||
value="#{demoBean.user.ville}"
|
||||
completeMethod="#{demoBean.completeCities}"
|
||||
placeholder="Commencez à taper..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldSpinner -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldSpinner</h5>
|
||||
<p class="text-600 mb-3">Compteur numérique avec incréments</p>
|
||||
|
||||
<fr:fieldSpinner id="quantite"
|
||||
label="Quantité"
|
||||
value="#{demoBean.user.quantite}"
|
||||
min="0"
|
||||
max="100"
|
||||
step="5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- fieldFileUpload -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:fieldFileUpload</h5>
|
||||
<p class="text-600 mb-3">Upload de fichiers avec contraintes</p>
|
||||
|
||||
<fr:fieldFileUpload id="document"
|
||||
label="Document"
|
||||
mode="simple"
|
||||
allowTypes="/(\.|\/)(pdf|doc|docx)$/"
|
||||
fileLimit="3" />
|
||||
<p class="text-600 text-sm mt-2">Formats acceptés: PDF, DOC, DOCX (max 3 fichiers)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION: COMPOSANTS D'ACTION - 5 composants
|
||||
================================================================ -->
|
||||
<div class="col-12 mt-5">
|
||||
<h2 class="section-title flex align-items-center gap-2">
|
||||
<i class="pi pi-bolt text-orange-500"></i>
|
||||
Composants d'Action
|
||||
<fr:tag value="5 composants" severity="warning" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- button -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:button</h5>
|
||||
<p class="text-600 mb-3">Bouton de navigation simple (sans Ajax)</p>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<fr:button value="Primary" severity="primary" icon="pi pi-check" />
|
||||
<fr:button value="Secondary" severity="secondary" icon="pi pi-times" />
|
||||
<fr:button value="Success" severity="success" icon="pi pi-check" />
|
||||
<fr:button value="Info" severity="info" icon="pi pi-info-circle" />
|
||||
<fr:button value="Warning" severity="warning" icon="pi pi-exclamation-triangle" />
|
||||
<fr:button value="Danger" severity="danger" icon="pi pi-trash" />
|
||||
<fr:button value="Help" severity="help" icon="pi pi-question-circle" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- commandButton -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:commandButton</h5>
|
||||
<p class="text-600 mb-3">Bouton avec action Ajax</p>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<fr:commandButton value="Valider" severity="success" icon="pi pi-check" action="#{demoBean.saveAction}" />
|
||||
<fr:commandButton value="Annuler" severity="secondary" icon="pi pi-times" action="#{demoBean.cancelAction}" />
|
||||
<fr:commandButton value="Rafraîchir" severity="info" icon="pi pi-refresh" action="#{demoBean.refreshAction}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- linkButton -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:linkButton</h5>
|
||||
<p class="text-600 mb-3">Bouton sous forme de lien</p>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<fr:linkButton value="Lien Primary" severity="primary" icon="pi pi-external-link" />
|
||||
<fr:linkButton value="Lien Secondary" severity="secondary" icon="pi pi-link" />
|
||||
<fr:linkButton value="Lien Info" severity="info" icon="pi pi-book" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- splitButton -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:splitButton</h5>
|
||||
<p class="text-600 mb-3">Bouton avec menu déroulant d'actions</p>
|
||||
|
||||
<fr:splitButton value="Enregistrer"
|
||||
icon="pi pi-save"
|
||||
severity="success"
|
||||
action="#{demoBean.saveAction}">
|
||||
<p:menuitem value="Enregistrer et Fermer" icon="pi pi-check" />
|
||||
<p:menuitem value="Enregistrer comme..." icon="pi pi-save" />
|
||||
<p:separator />
|
||||
<p:menuitem value="Annuler" icon="pi pi-times" />
|
||||
</fr:splitButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- actionDialog -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:actionDialog</h5>
|
||||
<p class="text-600 mb-3">Dialogue modal pour confirmation d'action</p>
|
||||
|
||||
<fr:commandButton value="Ouvrir Dialogue Action"
|
||||
icon="pi pi-question-circle"
|
||||
severity="warning"
|
||||
onclick="PF('actionDialogDemo').show()"
|
||||
type="button" />
|
||||
|
||||
<fr:actionDialog widgetVar="actionDialogDemo"
|
||||
header="Confirmation d'action"
|
||||
message="Êtes-vous sûr de vouloir effectuer cette action?"
|
||||
icon="pi pi-exclamation-triangle"
|
||||
confirmLabel="Confirmer"
|
||||
cancelLabel="Annuler"
|
||||
confirmAction="#{demoBean.confirmAction}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION: COMPOSANTS DE LAYOUT - 4 composants
|
||||
================================================================ -->
|
||||
<div class="col-12 mt-5">
|
||||
<h2 class="section-title flex align-items-center gap-2">
|
||||
<i class="pi pi-th-large text-green-500"></i>
|
||||
Composants de Layout
|
||||
<fr:tag value="4 composants" severity="success" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- card -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:card header="fr:card"
|
||||
subheader="Carte conteneur avec en-tête et pied">
|
||||
<p class="text-600">
|
||||
Ceci est le contenu de la carte. Les cartes sont utilisées pour regrouper
|
||||
des informations connexes dans un conteneur visuellement distinct.
|
||||
</p>
|
||||
|
||||
<f:facet name="footer">
|
||||
<div class="flex justify-content-end gap-2">
|
||||
<fr:commandButton value="Action 1" severity="secondary" />
|
||||
<fr:commandButton value="Action 2" severity="primary" />
|
||||
</div>
|
||||
</f:facet>
|
||||
</fr:card>
|
||||
</div>
|
||||
|
||||
<!-- panel -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:panel header="fr:panel"
|
||||
toggleable="true"
|
||||
collapsed="false">
|
||||
<p class="text-600">
|
||||
Panel pliable/dépliable pour organiser le contenu en sections.
|
||||
Cliquez sur l'icône pour replier/déplier.
|
||||
</p>
|
||||
</fr:panel>
|
||||
</div>
|
||||
|
||||
<!-- divider -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:divider</h5>
|
||||
<p class="text-600 mb-3">Séparateur visuel horizontal ou vertical</p>
|
||||
|
||||
<div class="mb-3">
|
||||
<p>Contenu avant le divider</p>
|
||||
<fr:divider />
|
||||
<p>Contenu après le divider</p>
|
||||
</div>
|
||||
|
||||
<fr:divider align="left">
|
||||
<span class="text-600 font-bold">Divider avec texte aligné à gauche</span>
|
||||
</fr:divider>
|
||||
|
||||
<fr:divider align="center">
|
||||
<fr:tag value="Section Centrale" severity="info" />
|
||||
</fr:divider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- spacer -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:spacer</h5>
|
||||
<p class="text-600 mb-3">Espace vide pour ajuster la mise en page</p>
|
||||
|
||||
<div class="flex align-items-center">
|
||||
<span>Élément gauche</span>
|
||||
<fr:spacer />
|
||||
<span>Élément droite</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION: COMPOSANTS DE NAVIGATION - 3 composants
|
||||
================================================================ -->
|
||||
<div class="col-12 mt-5">
|
||||
<h2 class="section-title flex align-items-center gap-2">
|
||||
<i class="pi pi-map text-indigo-500"></i>
|
||||
Composants de Navigation
|
||||
<fr:tag value="3 composants" severity="help" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- breadcrumb -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:breadcrumb</h5>
|
||||
<p class="text-600 mb-3">Fil d'Ariane pour navigation hiérarchique</p>
|
||||
|
||||
<fr:breadcrumb>
|
||||
<p:menuitem value="Accueil" icon="pi pi-home" url="#" />
|
||||
<p:menuitem value="Users" url="#" />
|
||||
<p:menuitem value="Liste" url="#" />
|
||||
</fr:breadcrumb>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- steps -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:steps</h5>
|
||||
<p class="text-600 mb-3">Indicateur de progression par étapes</p>
|
||||
|
||||
<fr:steps activeIndex="1">
|
||||
<p:menuitem value="Informations Personnelles" icon="pi pi-user" />
|
||||
<p:menuitem value="Coordonnées" icon="pi pi-map-marker" />
|
||||
<p:menuitem value="Confirmation" icon="pi pi-check" />
|
||||
</fr:steps>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tabView -->
|
||||
<div class="col-12">
|
||||
<fr:tabView>
|
||||
<p:tab title="fr:tabView - Onglet 1" icon="pi pi-calendar">
|
||||
<p class="text-600">
|
||||
Contenu du premier onglet. Les onglets permettent d'organiser le contenu en sections
|
||||
accessibles via des onglets cliquables.
|
||||
</p>
|
||||
</p:tab>
|
||||
<p:tab title="Onglet 2" icon="pi pi-user">
|
||||
<p class="text-600">
|
||||
Contenu du deuxième onglet avec informations différentes.
|
||||
</p>
|
||||
</p:tab>
|
||||
<p:tab title="Onglet 3" icon="pi pi-cog">
|
||||
<p class="text-600">
|
||||
Troisième onglet pour démonstration complète.
|
||||
</p>
|
||||
</p:tab>
|
||||
</fr:tabView>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION: COMPOSANTS DE DONNÉES - 5 composants
|
||||
================================================================ -->
|
||||
<div class="col-12 mt-5">
|
||||
<h2 class="section-title flex align-items-center gap-2">
|
||||
<i class="pi pi-table text-cyan-500"></i>
|
||||
Composants de Données
|
||||
<fr:tag value="5 composants" severity="info" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- dataTable -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:dataTable</h5>
|
||||
<p class="text-600 mb-3">Tableau de données avec tri, filtrage et pagination</p>
|
||||
|
||||
<fr:dataTable value="#{demoBean.sampleUsers}"
|
||||
var="user"
|
||||
paginator="true"
|
||||
rows="5">
|
||||
<p:column headerText="Nom" sortBy="#{user.nom}">
|
||||
<h:outputText value="#{user.nom}" />
|
||||
</p:column>
|
||||
<p:column headerText="Email" sortBy="#{user.email}">
|
||||
<h:outputText value="#{user.email}" />
|
||||
</p:column>
|
||||
<p:column headerText="Statut">
|
||||
<fr:tag value="#{user.actif ? 'Actif' : 'Inactif'}"
|
||||
severity="#{user.actif ? 'success' : 'danger'}" />
|
||||
</p:column>
|
||||
</fr:dataTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- dataView -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:dataView</h5>
|
||||
<p class="text-600 mb-3">Affichage de données en grille/liste avec templates</p>
|
||||
|
||||
<fr:dataView value="#{demoBean.sampleUsers}"
|
||||
var="user"
|
||||
rows="3"
|
||||
paginator="true">
|
||||
<p:dataViewGridItem>
|
||||
<div class="surface-50 p-3 border-round">
|
||||
<div class="flex align-items-center gap-2 mb-2">
|
||||
<i class="pi pi-user text-blue-500"></i>
|
||||
<span class="font-bold">#{user.nom}</span>
|
||||
</div>
|
||||
<p class="text-600 text-sm m-0">#{user.email}</p>
|
||||
</div>
|
||||
</p:dataViewGridItem>
|
||||
</fr:dataView>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tree -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:tree</h5>
|
||||
<p class="text-600 mb-3">Arborescence hiérarchique navigable</p>
|
||||
|
||||
<fr:tree value="#{demoBean.treeRoot}"
|
||||
var="node">
|
||||
<p:treeNode>
|
||||
<h:outputText value="#{node.label}" />
|
||||
</p:treeNode>
|
||||
</fr:tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- treeTable -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:treeTable</h5>
|
||||
<p class="text-600 mb-3">Tableau arborescent avec colonnes</p>
|
||||
|
||||
<fr:treeTable value="#{demoBean.treeRoot}"
|
||||
var="node">
|
||||
<p:column headerText="Nom">
|
||||
<h:outputText value="#{node.label}" />
|
||||
</p:column>
|
||||
<p:column headerText="Type">
|
||||
<h:outputText value="#{node.type}" />
|
||||
</p:column>
|
||||
</fr:treeTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- inplace -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:inplace</h5>
|
||||
<p class="text-600 mb-3">Édition en ligne activable au clic</p>
|
||||
|
||||
<fr:inplace label="Cliquez pour éditer">
|
||||
<p:inputText value="#{demoBean.inplaceText}" />
|
||||
</fr:inplace>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION: COMPOSANTS DE FEEDBACK - 3 composants
|
||||
================================================================ -->
|
||||
<div class="col-12 mt-5">
|
||||
<h2 class="section-title flex align-items-center gap-2">
|
||||
<i class="pi pi-comments text-pink-500"></i>
|
||||
Composants de Feedback
|
||||
<fr:tag value="3 composants" severity="danger" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- message -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:message</h5>
|
||||
<p class="text-600 mb-3">Message de validation pour champ spécifique</p>
|
||||
|
||||
<fr:fieldInput id="champAvecErreur"
|
||||
label="Champ requis"
|
||||
value="#{demoBean.requiredField}"
|
||||
required="true" />
|
||||
<fr:message for="champAvecErreur" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- growl -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:growl</h5>
|
||||
<p class="text-600 mb-3">Notifications toast en coin d'écran</p>
|
||||
|
||||
<fr:growl id="growlDemo" />
|
||||
<fr:commandButton value="Afficher Success"
|
||||
severity="success"
|
||||
action="#{demoBean.showSuccessMessage}"
|
||||
update=":formFreyaShowcase:growlDemo" />
|
||||
<fr:commandButton value="Afficher Warning"
|
||||
severity="warning"
|
||||
action="#{demoBean.showWarningMessage}"
|
||||
update=":formFreyaShowcase:growlDemo"
|
||||
styleClass="ml-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- progressBar -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:progressBar</h5>
|
||||
<p class="text-600 mb-3">Barre de progression pour opérations longues</p>
|
||||
|
||||
<fr:progressBar value="#{demoBean.progressValue}"
|
||||
displayValue="true"
|
||||
mode="determinate" />
|
||||
<div class="flex gap-2 mt-3">
|
||||
<fr:commandButton value="Démarrer"
|
||||
severity="primary"
|
||||
action="#{demoBean.startProgress}"
|
||||
update=":formFreyaShowcase" />
|
||||
<fr:commandButton value="Réinitialiser"
|
||||
severity="secondary"
|
||||
action="#{demoBean.resetProgress}"
|
||||
update=":formFreyaShowcase" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION: COMPOSANTS UTILITAIRES - 3 composants
|
||||
================================================================ -->
|
||||
<div class="col-12 mt-5">
|
||||
<h2 class="section-title flex align-items-center gap-2">
|
||||
<i class="pi pi-wrench text-teal-500"></i>
|
||||
Composants Utilitaires
|
||||
<fr:tag value="3 composants" severity="success" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- avatar -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:avatar</h5>
|
||||
<p class="text-600 mb-3">Avatar utilisateur avec image ou initiales</p>
|
||||
|
||||
<div class="flex gap-3 align-items-center flex-wrap">
|
||||
<fr:avatar label="JD" size="large" shape="circle" />
|
||||
<fr:avatar label="AB" size="normal" shape="circle" />
|
||||
<fr:avatar label="XY" size="small" shape="square" />
|
||||
<fr:avatar icon="pi pi-user" size="large" shape="circle" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- badge -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:badge</h5>
|
||||
<p class="text-600 mb-3">Badge de notification avec compteur</p>
|
||||
|
||||
<div class="flex gap-4 align-items-center flex-wrap">
|
||||
<fr:badge value="2" severity="danger">
|
||||
<i class="pi pi-bell" style="font-size: 2rem"></i>
|
||||
</fr:badge>
|
||||
<fr:badge value="5" severity="success">
|
||||
<i class="pi pi-envelope" style="font-size: 2rem"></i>
|
||||
</fr:badge>
|
||||
<fr:badge value="10" severity="warning">
|
||||
<i class="pi pi-shopping-cart" style="font-size: 2rem"></i>
|
||||
</fr:badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- tag -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:tag</h5>
|
||||
<p class="text-600 mb-3">Tags colorés pour statuts et catégories</p>
|
||||
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<fr:tag value="Primary" severity="primary" />
|
||||
<fr:tag value="Success" severity="success" icon="pi pi-check" />
|
||||
<fr:tag value="Info" severity="info" icon="pi pi-info-circle" />
|
||||
<fr:tag value="Warning" severity="warning" icon="pi pi-exclamation-triangle" />
|
||||
<fr:tag value="Danger" severity="danger" icon="pi pi-times" />
|
||||
<fr:tag value="Secondary" severity="secondary" />
|
||||
<fr:tag value="Help" severity="help" icon="pi pi-question-circle" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION: COMPOSANTS AVANCÉS - 3 composants
|
||||
================================================================ -->
|
||||
<div class="col-12 mt-5">
|
||||
<h2 class="section-title flex align-items-center gap-2">
|
||||
<i class="pi pi-chart-bar text-purple-500"></i>
|
||||
Composants Avancés
|
||||
<fr:tag value="3 composants" severity="help" />
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- chart -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:chart</h5>
|
||||
<p class="text-600 mb-3">Graphique avec Chart.js (bar, line, pie, etc.)</p>
|
||||
|
||||
<p:chart type="bar"
|
||||
responsive="true"
|
||||
style="height:300px">
|
||||
<f:facet name="chartConfig">
|
||||
#{demoBean.chartData}
|
||||
</f:facet>
|
||||
</p:chart>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- formDialog -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card component-card">
|
||||
<h5>fr:formDialog</h5>
|
||||
<p class="text-600 mb-3">Dialogue modal avec formulaire intégré</p>
|
||||
|
||||
<fr:commandButton value="Ouvrir Form Dialog"
|
||||
icon="pi pi-external-link"
|
||||
severity="primary"
|
||||
onclick="PF('formDialogDemo').show()"
|
||||
type="button" />
|
||||
|
||||
<fr:formDialog widgetVar="formDialogDemo"
|
||||
header="Créer un nouvel utilisateur"
|
||||
saveLabel="Créer"
|
||||
cancelLabel="Annuler"
|
||||
saveAction="#{demoBean.createUser}">
|
||||
<fr:fieldInput id="dialogNom"
|
||||
label="Nom"
|
||||
value="#{demoBean.dialogData.nom}"
|
||||
required="true" />
|
||||
<fr:fieldInput id="dialogEmail"
|
||||
label="Email"
|
||||
value="#{demoBean.dialogData.email}"
|
||||
required="true" />
|
||||
</fr:formDialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- themeSelector -->
|
||||
<div class="col-12">
|
||||
<div class="card component-card">
|
||||
<h5>fr:themeSelector</h5>
|
||||
<p class="text-600 mb-3">Sélecteur de thème Freya (16 variantes)</p>
|
||||
|
||||
<fr:themeSelector />
|
||||
<p class="text-600 text-sm mt-3">
|
||||
<i class="pi pi-info-circle mr-1"></i>
|
||||
Ce composant permet de changer dynamiquement le thème de l'application parmi
|
||||
les 16 variantes Freya (8 couleurs × 2 modes dark/light)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
FOOTER - RÉSUMÉ
|
||||
================================================================ -->
|
||||
<div class="col-12 mt-5">
|
||||
<div class="card bg-blue-50">
|
||||
<div class="text-center">
|
||||
<i class="pi pi-check-circle text-green-500 mb-3" style="font-size: 3rem"></i>
|
||||
<h3 class="text-900 mb-2">Intégration Complète Réussie</h3>
|
||||
<p class="text-600 mb-3">
|
||||
Les 46 composants PrimeFaces Freya Extension sont maintenant disponibles dans Lions User Manager
|
||||
</p>
|
||||
<div class="flex justify-content-center gap-2 flex-wrap">
|
||||
<fr:tag value="20 Formulaires" severity="info" icon="pi pi-inbox" />
|
||||
<fr:tag value="5 Actions" severity="warning" icon="pi pi-bolt" />
|
||||
<fr:tag value="4 Layout" severity="success" icon="pi pi-th-large" />
|
||||
<fr:tag value="3 Navigation" severity="help" icon="pi pi-map" />
|
||||
<fr:tag value="5 Données" severity="info" icon="pi pi-table" />
|
||||
<fr:tag value="3 Feedback" severity="danger" icon="pi pi-comments" />
|
||||
<fr:tag value="3 Utilitaires" severity="success" icon="pi pi-wrench" />
|
||||
<fr:tag value="3 Avancés" severity="help" icon="pi pi-chart-bar" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core">
|
||||
<f:metadata>
|
||||
<f:viewAction action="/pages/user-manager/users/list.xhtml?faces-redirect=true" />
|
||||
</f:metadata>
|
||||
<h:head>
|
||||
<title>Redirecting...</title>
|
||||
</h:head>
|
||||
<h:body>
|
||||
Redirecting to user list...
|
||||
</h:body>
|
||||
|
||||
</html>
|
||||
@@ -4,447 +4,163 @@
|
||||
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">
|
||||
|
||||
<ui:param name="page" value="#{roleGestionBean}"/>
|
||||
<ui:define name="title">Gestion des 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-shield text-purple-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Gestion des Rôles</h3>
|
||||
<p class="text-600 m-0">Gestion des rôles Realm et Client Keycloak</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<fr:commandButton value="Nouveau Rôle Realm"
|
||||
icon="pi pi-plus"
|
||||
severity="success"
|
||||
type="button"
|
||||
onclick="PF('createRealmRoleDialog').show();" />
|
||||
<fr:commandButton value="Nouveau Rôle Client"
|
||||
icon="pi pi-plus-circle"
|
||||
severity="info"
|
||||
type="button"
|
||||
onclick="PF('createClientRoleDialog').show();" />
|
||||
</div>
|
||||
<!-- En-tête -->
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-shield text-purple-500" />
|
||||
<ui:param name="title" value="Gestion des Rôles" />
|
||||
<ui:param name="description" value="Gestion des rôles Realm et Client Keycloak" />
|
||||
<ui:define name="actions">
|
||||
<h:form id="formActionsRoles">
|
||||
<div class="flex gap-2">
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Nouveau Rôle Realm" />
|
||||
<ui:param name="icon" value="pi pi-plus" />
|
||||
<ui:param name="hasAction" value="false" />
|
||||
<ui:param name="hasOutcome" value="false" />
|
||||
<ui:param name="onclick" value="PF('createRealmRoleDialog').show()" />
|
||||
<ui:param name="severity" value="success" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Nouveau Rôle Client" />
|
||||
<ui:param name="icon" value="pi pi-plus-circle" />
|
||||
<ui:param name="hasAction" value="false" />
|
||||
<ui:param name="hasOutcome" value="false" />
|
||||
<ui:param name="onclick" value="PF('createClientRoleDialog').show()" />
|
||||
<ui:param name="severity" value="info" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
|
||||
<!-- ================================================================
|
||||
FILTRES & KPI INTÉGRÉS (Nombre d'or φ = 1.618 → 62%/38%)
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="grid">
|
||||
<!-- =========== 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>
|
||||
<!-- Filtres -->
|
||||
<div class="card mb-3">
|
||||
<h:form id="formFilters">
|
||||
<p:panelGrid columns="3" styleClass="w-full" columnClasses="col-12 md:col-4">
|
||||
<p:outputLabel for="realmFilter" value="Realm" />
|
||||
<p:selectOneMenu id="realmFilter"
|
||||
value="#{roleGestionBean.realmName}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<f:selectItems value="#{roleGestionBean.availableRealms}" />
|
||||
<p:ajax event="change"
|
||||
listener="#{roleGestionBean.loadRealmRoles}"
|
||||
update=":formRealmRoles:realmRolesPanel :formClientRoles:clientRolesPanel" />
|
||||
</p:selectOneMenu>
|
||||
|
||||
<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>
|
||||
<p:outputLabel for="clientFilter" value="Client" />
|
||||
<p:selectOneMenu id="clientFilter"
|
||||
value="#{roleGestionBean.clientName}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<f:selectItems value="#{roleGestionBean.availableClients}" />
|
||||
<p:ajax event="change"
|
||||
listener="#{roleGestionBean.loadClientRoles}"
|
||||
update=":formClientRoles:clientRolesPanel" />
|
||||
</p:selectOneMenu>
|
||||
|
||||
<!-- 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>
|
||||
</h:form>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
RÔLES REALM
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h:form id="formRealmRoles">
|
||||
<div class="flex align-items-center justify-content-between mb-4">
|
||||
<h3 class="text-900 font-semibold text-lg m-0 flex align-items-center gap-2">
|
||||
<i class="pi pi-shield text-purple-500"></i>
|
||||
Rôles Realm
|
||||
</h3>
|
||||
<fr:commandButton value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
outlined="true"
|
||||
size="small"
|
||||
action="#{roleGestionBean.loadRealmRoles}"
|
||||
update=":formRealmRoles :formKpis" />
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<ui:repeat value="#{roleGestionBean.realmRoles}" var="role">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div class="flex-grow-1">
|
||||
<h4 class="text-900 font-semibold m-0 mb-2 flex align-items-center gap-2">
|
||||
<i class="pi pi-tag text-purple-500"></i>
|
||||
<span>#{role.name}</span>
|
||||
</h4>
|
||||
<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>
|
||||
<div class="flex align-items-center gap-1">
|
||||
<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" />
|
||||
</fr:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 mb-2">
|
||||
<span class="inline-flex align-items-center bg-purple-100 text-purple-700 px-2 py-1 border-round text-xs font-semibold">
|
||||
<i class="pi pi-globe mr-1"></i>
|
||||
REALM
|
||||
</span>
|
||||
<span class="inline-flex align-items-center bg-blue-100 text-blue-700 px-2 py-1 border-round text-xs" rendered="#{role.composite}">
|
||||
<i class="pi pi-sitemap mr-1"></i>
|
||||
COMPOSITE
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-500 text-xs">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span class="ml-1">ID: #{role.id != null ? role.id : 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:repeat>
|
||||
|
||||
<!-- Message si aucun rôle -->
|
||||
<div class="col-12" rendered="#{roleGestionBean.realmRoles == null or roleGestionBean.realmRoles.size() == 0}">
|
||||
<div class="text-center p-4">
|
||||
<i class="pi pi-inbox text-400" style="font-size: 3rem"></i>
|
||||
<p class="text-600 mt-3 mb-0">Aucun rôle Realm trouvé</p>
|
||||
<small class="text-500">Sélectionnez un realm ou créez un nouveau rôle</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
RÔLES CLIENT
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h:form id="formClientRoles">
|
||||
<div class="flex align-items-center justify-content-between mb-4">
|
||||
<h3 class="text-900 font-semibold text-lg m-0 flex align-items-center gap-2">
|
||||
<i class="pi pi-sitemap text-blue-500"></i>
|
||||
Rôles Client
|
||||
</h3>
|
||||
<fr:commandButton value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
outlined="true"
|
||||
size="small"
|
||||
action="#{roleGestionBean.loadClientRoles}"
|
||||
update=":formClientRoles"
|
||||
disabled="#{roleGestionBean.clientName == null or roleGestionBean.clientName == ''}" />
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<ui:repeat value="#{roleGestionBean.clientRoles}" var="role">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div class="flex-grow-1">
|
||||
<h4 class="text-900 font-semibold m-0 mb-2 flex align-items-center gap-2">
|
||||
<i class="pi pi-tag text-blue-500"></i>
|
||||
<span>#{role.name}</span>
|
||||
</h4>
|
||||
<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>
|
||||
<div class="flex align-items-center gap-1">
|
||||
<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" />
|
||||
</fr:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 mb-2">
|
||||
<span class="inline-flex align-items-center bg-blue-100 text-blue-700 px-2 py-1 border-round text-xs font-semibold">
|
||||
<i class="pi pi-box mr-1"></i>
|
||||
CLIENT
|
||||
</span>
|
||||
<span class="inline-flex align-items-center bg-green-100 text-green-700 px-2 py-1 border-round text-xs" rendered="#{role.composite}">
|
||||
<i class="pi pi-sitemap mr-1"></i>
|
||||
COMPOSITE
|
||||
</span>
|
||||
<span class="inline-flex align-items-center bg-orange-100 text-orange-700 px-2 py-1 border-round text-xs" rendered="#{role.clientName != null}">
|
||||
#{role.clientName}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-500 text-xs">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span class="ml-1">ID: #{role.id != null ? role.id : 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:repeat>
|
||||
|
||||
<!-- Message si aucun rôle -->
|
||||
<div class="col-12" rendered="#{roleGestionBean.clientRoles == null or roleGestionBean.clientRoles.size() == 0}">
|
||||
<div class="text-center p-4">
|
||||
<i class="pi pi-inbox text-400" style="font-size: 3rem"></i>
|
||||
<p class="text-600 mt-3 mb-0">Aucun rôle Client trouvé</p>
|
||||
<small class="text-500">Sélectionnez un client ou créez un nouveau rôle</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
<p:outputLabel for="typeFilter" value="Type" />
|
||||
<p:selectOneMenu id="typeFilter"
|
||||
value="#{roleGestionBean.selectedTypeRole}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les types" itemValue="" />
|
||||
<f:selectItems value="#{roleGestionBean.typeRoleOptions}" />
|
||||
</p:selectOneMenu>
|
||||
</p:panelGrid>
|
||||
</h:form>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG CRÉATION RÔLE REALM
|
||||
================================================================ -->
|
||||
<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>
|
||||
<!-- Rôles Realm -->
|
||||
<div class="card mb-3">
|
||||
<h:form id="formRealmRoles">
|
||||
<p:panel id="realmRolesPanel" header="Rôles Realm" toggleable="true" collapsed="false">
|
||||
<div class="grid">
|
||||
<c:forEach var="role" items="#{roleGestionBean.realmRoles}">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<ui:include src="/templates/components/role-management/role-card.xhtml">
|
||||
<ui:param name="role" value="#{role}" />
|
||||
<ui:param name="showActions" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</c:forEach>
|
||||
<c:if test="#{empty roleGestionBean.realmRoles}">
|
||||
<div class="col-12">
|
||||
<p class="text-center text-color-secondary">Aucun rôle Realm trouvé</p>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</p:panel>
|
||||
</h:form>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<!-- Rôles Client -->
|
||||
<div class="card">
|
||||
<h:form id="formClientRoles">
|
||||
<p:panel id="clientRolesPanel" header="Rôles Client" toggleable="true" collapsed="false">
|
||||
<div class="grid">
|
||||
<c:forEach var="role" items="#{roleGestionBean.clientRoles}">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<ui:include src="/templates/components/role-management/role-card.xhtml">
|
||||
<ui:param name="role" value="#{role}" />
|
||||
<ui:param name="showActions" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</c:forEach>
|
||||
<c:if test="#{empty roleGestionBean.clientRoles}">
|
||||
<div class="col-12">
|
||||
<p class="text-center text-color-secondary">Aucun rôle Client trouvé</p>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</p:panel>
|
||||
</h:form>
|
||||
</div>
|
||||
|
||||
<fr:message id="messagesRealmRole" showDetail="true" closable="true">
|
||||
<p:autoUpdate />
|
||||
</fr:message>
|
||||
</fr:formDialog>
|
||||
<!-- Dialog Création Rôle Realm -->
|
||||
<p:dialog id="createRealmRoleDialog"
|
||||
header="Nouveau Rôle Realm"
|
||||
widgetVar="createRealmRoleDialog"
|
||||
modal="true"
|
||||
styleClass="w-full md:w-6">
|
||||
<h:form id="formCreateRealmRole">
|
||||
<ui:include src="/templates/components/role-management/role-form.xhtml">
|
||||
<ui:param name="role" value="#{roleGestionBean.newRole}" />
|
||||
<ui:param name="mode" value="create" />
|
||||
<ui:param name="showClientSelector" value="false" />
|
||||
<ui:param name="submitAction" value="#{roleGestionBean.createRealmRole}" />
|
||||
<ui:param name="hasSubmitAction" value="true" />
|
||||
<ui:param name="update" value=":formRealmRoles:realmRolesPanel" />
|
||||
<ui:param name="useParentForm" value="true" />
|
||||
</ui:include>
|
||||
</h:form>
|
||||
</p:dialog>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG CRÉATION RÔLE CLIENT
|
||||
================================================================ -->
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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 (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">
|
||||
<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>
|
||||
<!-- Dialog Création Rôle Client -->
|
||||
<p:dialog id="createClientRoleDialog"
|
||||
header="Nouveau Rôle Client"
|
||||
widgetVar="createClientRoleDialog"
|
||||
modal="true"
|
||||
styleClass="w-full md:w-6">
|
||||
<h:form id="formCreateClientRole">
|
||||
<ui:include src="/templates/components/role-management/role-form.xhtml">
|
||||
<ui:param name="role" value="#{roleGestionBean.newRole}" />
|
||||
<ui:param name="mode" value="create" />
|
||||
<ui:param name="showClientSelector" value="true" />
|
||||
<ui:param name="submitAction" value="#{roleGestionBean.createClientRole}" />
|
||||
<ui:param name="hasSubmitAction" value="true" />
|
||||
<ui:param name="update" value=":formClientRoles:clientRolesPanel" />
|
||||
<ui:param name="useParentForm" value="true" />
|
||||
</ui:include>
|
||||
</h:form>
|
||||
</p:dialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
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">
|
||||
|
||||
@@ -25,43 +24,31 @@
|
||||
<div class="card">
|
||||
<h5>Informations du compte</h5>
|
||||
<h:form id="formAccountInfo">
|
||||
<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: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="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="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="fullName"
|
||||
label="Nom complet"
|
||||
value="#{userSessionBean.fullName}"
|
||||
disabled="true"
|
||||
iconLeft="pi pi-id-card"
|
||||
helpText="Votre nom complet" />
|
||||
</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="mainRole"
|
||||
label="Rôle principal"
|
||||
value="#{userSessionBean.mainRole}"
|
||||
disabled="true"
|
||||
iconLeft="pi pi-shield"
|
||||
helpText="Votre rôle dans l'application" />
|
||||
</div>
|
||||
</div>
|
||||
<p:outputLabel for="mainRole" value="Rôle principal" />
|
||||
<p:inputText id="mainRole"
|
||||
value="#{userSessionBean.mainRole}"
|
||||
readonly="true"
|
||||
styleClass="w-full" />
|
||||
</p:panelGrid>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,34 +59,35 @@
|
||||
<h5>Préférences</h5>
|
||||
<h:form id="formPreferences">
|
||||
<div class="flex flex-column gap-3">
|
||||
<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 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>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
@@ -109,26 +97,29 @@
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h5>Actions</h5>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex gap-2">
|
||||
<h:form>
|
||||
<fr:commandButton value="Rafraîchir les informations"
|
||||
icon="pi pi-refresh"
|
||||
severity="secondary"
|
||||
action="#{userSessionBean.loadUserInfo}"
|
||||
update="formAccountInfo" />
|
||||
<p:commandButton
|
||||
value="Rafraîchir les informations"
|
||||
icon="pi pi-refresh"
|
||||
styleClass="p-button-secondary"
|
||||
action="#{userSessionBean.loadUserInfo}"
|
||||
update="formAccountInfo" />
|
||||
</h:form>
|
||||
<h:form>
|
||||
<fr:commandButton value="Changer le mot de passe"
|
||||
icon="pi pi-key"
|
||||
severity="info"
|
||||
outcome="/pages/user-manager/users/profile" />
|
||||
<p:commandButton
|
||||
value="Changer le mot de passe"
|
||||
icon="pi pi-key"
|
||||
styleClass="p-button-info"
|
||||
outcome="/pages/user-manager/users/profile" />
|
||||
</h:form>
|
||||
<h:form>
|
||||
<fr:commandButton value="Sauvegarder les préférences"
|
||||
icon="pi pi-save"
|
||||
severity="success"
|
||||
action="#{settingsBean.savePreferences}"
|
||||
update="@form" />
|
||||
<p:commandButton
|
||||
value="Sauvegarder les préférences"
|
||||
icon="pi pi-save"
|
||||
styleClass="p-button-success"
|
||||
action="#{settingsBean.savePreferences}"
|
||||
update="@form" />
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,50 +1,99 @@
|
||||
<!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">
|
||||
<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" template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:define name="title">Synchronisation Keycloak - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<!-- En-tête -->
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-sync text-blue-500" />
|
||||
<ui:param name="title" value="Synchronisation Keycloak" />
|
||||
<ui:param name="description" value="Synchronisation et vérification de l'état de Keycloak" />
|
||||
</ui:include>
|
||||
<h:form id="syncForm">
|
||||
<p:growl id="growl" showDetail="true" />
|
||||
|
||||
<!-- Health Checks -->
|
||||
<div class="grid mb-4">
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card">
|
||||
<h5>État de Keycloak</h5>
|
||||
<p:outputLabel value="Vérification de la connexion..." />
|
||||
<!-- TODO: Intégrer SyncServiceClient pour afficher le statut -->
|
||||
</div>
|
||||
<!-- En-tête -->
|
||||
<div class="col-12">
|
||||
<ui:decorate template="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-sync text-blue-500" />
|
||||
<ui:param name="title" value="Synchronisation Keycloak" />
|
||||
<ui:param name="description" value="Synchronisation et vérification de l'état de Keycloak" />
|
||||
<ui:define name="actions">
|
||||
<p:commandButton value="Actualiser l'état" icon="pi pi-refresh"
|
||||
actionListener="#{syncDashboardBean.checkKeycloakStatus}" update="@form"
|
||||
styleClass="p-button-outlined" />
|
||||
</ui:define>
|
||||
</ui:decorate>
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card">
|
||||
<h5>Actions de Synchronisation</h5>
|
||||
<div class="flex flex-column gap-2">
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Synchroniser Utilisateurs" />
|
||||
<ui:param name="icon" value="pi pi-users" />
|
||||
<ui:param name="severity" value="primary" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Synchroniser Rôles" />
|
||||
<ui:param name="icon" value="pi pi-shield" />
|
||||
<ui:param name="severity" value="info" />
|
||||
</ui:include>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Health Check Card -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card h-full">
|
||||
<h5>État de Keycloak</h5>
|
||||
<div class="flex align-items-center gap-3 mb-3">
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-round p-3"
|
||||
style="width: 3rem; height: 3rem">
|
||||
<i class="pi pi-server text-blue-500 text-xl"></i>
|
||||
</div>
|
||||
<div class="flex flex-column">
|
||||
<span class="text-900 font-medium text-xl">
|
||||
#{syncDashboardBean.keycloakStatusLabel eq 'UP' ? 'En Ligne' : 'Hors Ligne'}
|
||||
</span>
|
||||
<span class="text-600">Statut du service</span>
|
||||
</div>
|
||||
<span class="ml-auto">
|
||||
<p:tag value="#{syncDashboardBean.keycloakStatusLabel}"
|
||||
severity="#{syncDashboardBean.keycloakStatusLabel eq 'UP' ? 'success' : 'danger'}" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p:divider />
|
||||
|
||||
<div class="flex flex-column gap-2">
|
||||
<div class="flex justify-content-between">
|
||||
<span class="text-600">Message:</span>
|
||||
<span class="text-900">#{syncDashboardBean.keycloakStatusMessage}</span>
|
||||
</div>
|
||||
<div class="flex justify-content-between">
|
||||
<span class="text-600">Version:</span>
|
||||
<span class="text-900">#{syncDashboardBean.keycloakVersion}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sync Actions Card -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="card h-full">
|
||||
<h5>Actions de Synchronisation</h5>
|
||||
<p class="text-600 mb-4">Lancez la synchronisation des données depuis Keycloak vers la base
|
||||
locale.</p>
|
||||
|
||||
<div class="flex flex-column gap-3">
|
||||
<div
|
||||
class="flex align-items-center justify-content-between border-1 surface-border border-round p-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-users text-primary text-xl"></i>
|
||||
<span class="text-900 font-medium">Utilisateurs</span>
|
||||
</div>
|
||||
<p:commandButton value="Synchroniser" icon="pi pi-sync"
|
||||
actionListener="#{syncDashboardBean.syncUsers}" update="growl"
|
||||
styleClass="p-button-rounded p-button-text" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex align-items-center justify-content-between border-1 surface-border border-round p-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-shield text-primary text-xl"></i>
|
||||
<span class="text-900 font-medium">Rôles</span>
|
||||
</div>
|
||||
<p:commandButton value="Synchroniser" icon="pi pi-sync"
|
||||
actionListener="#{syncDashboardBean.syncRoles}" update="growl"
|
||||
styleClass="p-button-rounded p-button-text p-button-secondary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,13 +1,9 @@
|
||||
<!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">
|
||||
<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" template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{userCreationBean}"/>
|
||||
<ui:param name="page" value="#{userCreationBean}" />
|
||||
<ui:define name="title">Nouvel Utilisateur - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
@@ -22,24 +18,13 @@
|
||||
<i class="pi pi-user-plus text-green-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Nouvel Utilisateur</h3>
|
||||
<p class="text-600 m-0">Créer un nouvel utilisateur dans le realm Keycloak</p>
|
||||
<p class="text-600 m-0">Créer un nouvel utilisateur dans Keycloak</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<fr:commandButton
|
||||
icon="pi pi-question-circle"
|
||||
rounded="true"
|
||||
text="true"
|
||||
severity="help"
|
||||
title="Aide"
|
||||
type="button"
|
||||
onclick="PF('helpDialog').show();" />
|
||||
<fr:button value="Retour"
|
||||
icon="pi pi-arrow-left"
|
||||
severity="secondary"
|
||||
text="true"
|
||||
outcome="/pages/user-manager/users/list" />
|
||||
</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>
|
||||
@@ -47,261 +32,142 @@
|
||||
<!-- ================================================================
|
||||
FORMULAIRE DE CRÉATION
|
||||
================================================================ -->
|
||||
<h:form id="formUserCreation">
|
||||
<!-- Messages globaux -->
|
||||
<h:form id="formUserCreation" styleClass="col-12 grid m-0 p-0">
|
||||
<div class="col-12">
|
||||
<fr:growl id="formMessages" />
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-info-circle text-blue-500"></i>
|
||||
Informations de l'Utilisateur
|
||||
</h3>
|
||||
|
||||
<!-- Informations de Base et Mot de Passe -->
|
||||
<div class="col-12 lg:col-8">
|
||||
<div class="grid">
|
||||
<!-- Section Informations de Base -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center gap-2 mb-4">
|
||||
<i class="pi pi-user text-blue-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Informations de Base</h5>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<!-- Colonne gauche: Informations de base -->
|
||||
<div class="col-12 lg:col-6">
|
||||
<div class="surface-50 border-round p-3 mb-3">
|
||||
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
|
||||
<i class="pi pi-user text-blue-500"></i>
|
||||
<span>Informations de Base</span>
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Nom d'utilisateur -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Nom d'utilisateur"
|
||||
value="#{userCreationBean.newUser.username}"
|
||||
required="true"
|
||||
placeholder="ex: jdupont"
|
||||
helpText="Identifiant unique de connexion (3-50 caractères)">
|
||||
<f:validateLength for="input" minimum="3" maximum="50" />
|
||||
</fr:fieldInput>
|
||||
<div class="field mb-3">
|
||||
<label for="username" class="block text-900 font-medium mb-2">
|
||||
Nom d'utilisateur <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:inputText id="username" value="#{userCreationBean.newUser.username}"
|
||||
styleClass="w-full" required="true" placeholder="ex: jdupont">
|
||||
<f:validateLength minimum="3" maximum="50" />
|
||||
</p:inputText>
|
||||
<small class="text-500">Identifiant unique de connexion</small>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Adresse email"
|
||||
value="#{userCreationBean.newUser.email}"
|
||||
required="true"
|
||||
type="email"
|
||||
placeholder="ex: jean.dupont@example.com"
|
||||
helpText="Adresse email valide">
|
||||
<f:validateRegex for="input" pattern="^[A-Za-z0-9+_.-]+@(.+)$" />
|
||||
</fr:fieldInput>
|
||||
<div class="field mb-3">
|
||||
<label for="email" class="block text-900 font-medium mb-2">
|
||||
Adresse email <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:inputText id="email" value="#{userCreationBean.newUser.email}"
|
||||
styleClass="w-full" required="true" type="email"
|
||||
placeholder="ex: jean.dupont@example.com">
|
||||
<f:validateRegex pattern="^[A-Za-z0-9+_.-]+@(.+)$" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
|
||||
<!-- Prénom -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Prénom"
|
||||
value="#{userCreationBean.newUser.prenom}"
|
||||
required="true"
|
||||
placeholder="ex: Jean"
|
||||
helpText="Prénom de l'utilisateur">
|
||||
<f:validateLength for="input" minimum="2" maximum="100" />
|
||||
</fr:fieldInput>
|
||||
<div class="field mb-3">
|
||||
<label for="prenom" class="block text-900 font-medium mb-2">
|
||||
Prénom <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:inputText id="prenom" value="#{userCreationBean.newUser.prenom}"
|
||||
styleClass="w-full" required="true" placeholder="ex: Jean">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
|
||||
<!-- Nom -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Nom"
|
||||
value="#{userCreationBean.newUser.nom}"
|
||||
required="true"
|
||||
placeholder="ex: Dupont"
|
||||
helpText="Nom de famille de l'utilisateur">
|
||||
<f:validateLength for="input" minimum="2" maximum="100" />
|
||||
</fr:fieldInput>
|
||||
<div class="field mb-0">
|
||||
<label for="nom" class="block text-900 font-medium mb-2">
|
||||
Nom <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:inputText id="nom" value="#{userCreationBean.newUser.nom}"
|
||||
styleClass="w-full" required="true" placeholder="ex: Dupont">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section Mot de Passe -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center gap-2 mb-4">
|
||||
<i class="pi pi-key text-orange-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Sécurité</h5>
|
||||
</div>
|
||||
<!-- Colonne droite: Mot de passe et Configuration -->
|
||||
<div class="col-12 lg:col-6">
|
||||
<!-- Section Mot de passe -->
|
||||
<div class="surface-50 border-round p-3 mb-3 ui-fluid">
|
||||
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
|
||||
<i class="pi pi-key text-orange-500"></i>
|
||||
<span>Mot de Passe</span>
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Mot de passe -->
|
||||
<div class="col-12 md:col-6">
|
||||
<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 class="field mb-3">
|
||||
<label for="password" class="block text-900 font-medium mb-2">
|
||||
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"
|
||||
placeholder="Minimum 8 caractères">
|
||||
<f:validateLength minimum="8" maximum="100" />
|
||||
</p:password>
|
||||
<small class="text-500">Au moins 8 caractères</small>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation mot de passe -->
|
||||
<div class="col-12 md:col-6">
|
||||
<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>
|
||||
|
||||
<!-- Info de sécurité -->
|
||||
<div class="surface-blue-50 border-round p-3 mt-3">
|
||||
<div class="flex align-items-start gap-2">
|
||||
<i class="pi pi-info-circle text-blue-500"></i>
|
||||
<div>
|
||||
<div class="text-blue-900 font-semibold text-sm mb-1">Recommandations de sécurité</div>
|
||||
<small class="text-blue-700">
|
||||
Utilisez un mot de passe fort contenant des majuscules, minuscules, chiffres et caractères spéciaux.
|
||||
L'utilisateur pourra le modifier lors de sa première connexion.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section Configuration -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center gap-2 mb-4">
|
||||
<i class="pi pi-cog text-purple-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Configuration</h5>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Realm -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldSelect
|
||||
label="Realm Keycloak"
|
||||
value="#{userCreationBean.realmName}"
|
||||
helpText="Espace d'administration Keycloak">
|
||||
<f:selectItems value="#{userCreationBean.availableRealms}"
|
||||
var="realm"
|
||||
itemLabel="#{realm}"
|
||||
itemValue="#{realm}" />
|
||||
</fr:fieldSelect>
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div class="col-12 md:col-6">
|
||||
<label class="block text-900 font-medium mb-2">
|
||||
<i class="pi pi-check-square text-500 mr-1"></i>
|
||||
Options
|
||||
<div class="field mb-0">
|
||||
<label for="passwordConfirm" class="block text-900 font-medium mb-2">
|
||||
Confirmer le mot de passe <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex flex-column gap-3">
|
||||
<!-- Compte activé -->
|
||||
<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é -->
|
||||
<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>
|
||||
<p:password id="passwordConfirm" value="#{userCreationBean.passwordConfirm}"
|
||||
styleClass="w-full" required="true" feedback="false" toggleMask="true"
|
||||
placeholder="Confirmer le mot de passe">
|
||||
</p:password>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
APERÇU ET RÉSUMÉ
|
||||
================================================================ -->
|
||||
<div class="col-12 lg:col-4">
|
||||
<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>
|
||||
</div>
|
||||
<!-- Section Configuration -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-purple-500"></i>
|
||||
<span>Configuration</span>
|
||||
</h4>
|
||||
|
||||
<!-- 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);">
|
||||
<h:outputText value="#{userCreationBean.newUser.username != null and userCreationBean.newUser.username.length() > 1 ? userCreationBean.newUser.username.substring(0,2).toUpperCase() : 'NU'}" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Realm -->
|
||||
<div class="field mb-3">
|
||||
<label for="realm" class="block text-900 font-medium mb-2">
|
||||
Realm Keycloak
|
||||
</label>
|
||||
<p:selectOneMenu id="realm" value="#{userCreationBean.realmName}"
|
||||
styleClass="w-full">
|
||||
<f:selectItems value="#{userCreationBean.availableRealms}" var="realm"
|
||||
itemLabel="#{realm}" itemValue="#{realm}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<!-- Résumé des informations -->
|
||||
<div class="flex flex-column gap-3">
|
||||
<!-- Username -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Nom d'utilisateur</small>
|
||||
<div class="font-semibold text-900">
|
||||
<h:outputText value="#{userCreationBean.newUser.username != null and userCreationBean.newUser.username != '' ? userCreationBean.newUser.username : '-'}" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- Options de configuration -->
|
||||
<div class="flex flex-column gap-2">
|
||||
<!-- Compte activé -->
|
||||
<div class="field-checkbox mb-0">
|
||||
<p:selectBooleanCheckbox id="enabled"
|
||||
value="#{userCreationBean.newUser.enabled}">
|
||||
</p:selectBooleanCheckbox>
|
||||
<label for="enabled" class="ml-2">Compte activé</label>
|
||||
</div>
|
||||
|
||||
<!-- Nom complet -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Nom complet</small>
|
||||
<div class="font-semibold text-900">
|
||||
<h:outputText value="#{(userCreationBean.newUser.prenom != null and userCreationBean.newUser.prenom != '') or (userCreationBean.newUser.nom != null and userCreationBean.newUser.nom != '') ? (userCreationBean.newUser.prenom != null ? userCreationBean.newUser.prenom : '') += ' ' += (userCreationBean.newUser.nom != null ? userCreationBean.newUser.nom : '') : '-'}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Email</small>
|
||||
<div class="font-semibold text-900" style="word-break: break-all;">
|
||||
<h:outputText value="#{userCreationBean.newUser.email != null and userCreationBean.newUser.email != '' ? userCreationBean.newUser.email : '-'}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statut -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Statut</small>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<fr:tag value="#{userCreationBean.newUser.enabled ? 'Activé' : 'Désactivé'}"
|
||||
severity="#{userCreationBean.newUser.enabled ? 'success' : 'secondary'}" />
|
||||
<fr:tag value="#{userCreationBean.newUser.emailVerified ? 'Email vérifié' : 'Email non vérifié'}"
|
||||
severity="#{userCreationBean.newUser.emailVerified ? 'info' : 'warning'}"
|
||||
styleClass="text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Realm -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Realm</small>
|
||||
<div class="font-semibold text-900">
|
||||
<i class="pi pi-globe text-purple-500 mr-1"></i>
|
||||
#{userCreationBean.realmName}
|
||||
<!-- Email vérifié -->
|
||||
<div class="field-checkbox mb-0">
|
||||
<p:selectBooleanCheckbox id="emailVerified"
|
||||
value="#{userCreationBean.newUser.emailVerified}">
|
||||
</p:selectBooleanCheckbox>
|
||||
<label for="emailVerified" class="ml-2">Email vérifié</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -313,43 +179,47 @@
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex flex-wrap gap-2 align-items-center justify-content-between">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<!-- Bouton Créer -->
|
||||
<fr:commandButton value="Créer l'utilisateur"
|
||||
icon="pi pi-check"
|
||||
severity="success"
|
||||
action="#{userCreationBean.createUser}"
|
||||
update=":formUserCreation"
|
||||
validateClient="true" />
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-check-circle text-green-500"></i>
|
||||
Actions
|
||||
</h3>
|
||||
|
||||
<!-- Bouton Réinitialiser -->
|
||||
<fr:commandButton value="Réinitialiser"
|
||||
icon="pi pi-refresh"
|
||||
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" />
|
||||
</fr:commandButton>
|
||||
<div class="flex flex-wrap gap-2 align-items-center">
|
||||
<!-- Bouton Créer -->
|
||||
<p:commandButton value="Créer l'utilisateur" icon="pi pi-check"
|
||||
styleClass="p-button-success" action="#{userCreationBean.createUser}"
|
||||
update=":formUserCreation" validateClient="true">
|
||||
</p:commandButton>
|
||||
|
||||
<!-- Bouton Annuler -->
|
||||
<fr:commandButton value="Annuler"
|
||||
icon="pi pi-times"
|
||||
outlined="true"
|
||||
outcome="/pages/user-manager/users/list"
|
||||
immediate="true" />
|
||||
</div>
|
||||
<!-- Bouton Réinitialiser -->
|
||||
<p:commandButton value="Réinitialiser" icon="pi pi-refresh"
|
||||
styleClass="p-button-outlined p-button-secondary" 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>
|
||||
|
||||
<!-- Info champs requis -->
|
||||
<div class="flex align-items-center gap-2 text-500 text-sm">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span><span class="text-red-500">*</span> Champs obligatoires</span>
|
||||
</div>
|
||||
<!-- Bouton Annuler -->
|
||||
<p:commandButton value="Annuler" icon="pi pi-times" styleClass="p-button-outlined"
|
||||
action="#{userCreationBean.cancel}" immediate="true">
|
||||
<p:confirm header="Confirmation" message="Voulez-vous vraiment annuler la création ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
|
||||
<div class="flex-grow-1"></div>
|
||||
|
||||
<!-- Aide -->
|
||||
<p:commandButton value="Aide" icon="pi pi-question-circle"
|
||||
styleClass="p-button-outlined p-button-help" type="button"
|
||||
onclick="PF('helpDialog').show();">
|
||||
</p:commandButton>
|
||||
</div>
|
||||
|
||||
<!-- Message d'information -->
|
||||
<p:messages id="messages" showDetail="true" closable="true" styleClass="mt-3">
|
||||
<p:autoUpdate />
|
||||
</p:messages>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
@@ -358,101 +228,55 @@
|
||||
<!-- ================================================================
|
||||
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-primary"
|
||||
icon="pi pi-check" />
|
||||
<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>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG D'AIDE
|
||||
================================================================ -->
|
||||
<p:dialog header="Guide de Création d'Utilisateur"
|
||||
widgetVar="helpDialog"
|
||||
modal="true"
|
||||
responsive="true"
|
||||
styleClass="w-full md:w-40rem"
|
||||
showEffect="fade"
|
||||
hideEffect="fade">
|
||||
<div class="flex flex-column gap-4">
|
||||
<!-- Section Informations Requises -->
|
||||
<div>
|
||||
<div class="flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-info-circle text-blue-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Informations Requises</h5>
|
||||
</div>
|
||||
<div class="surface-50 border-round p-3">
|
||||
<ul class="text-700 line-height-3 m-0 pl-3">
|
||||
<li class="mb-2">
|
||||
<strong>Nom d'utilisateur</strong> : Identifiant unique de 3 à 50 caractères.
|
||||
<small class="block text-500 mt-1">Ex: jdupont, marie.martin</small>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<strong>Email</strong> : Adresse email valide et unique.
|
||||
<small class="block text-500 mt-1">Ex: utilisateur@example.com</small>
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<strong>Prénom et Nom</strong> : Identification complète de l'utilisateur.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Mot de passe</strong> : Au moins 8 caractères requis.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p:dialog header="Aide - Création d'Utilisateur" widgetVar="helpDialog" modal="true" responsive="true"
|
||||
width="600" showEffect="fade" hideEffect="fade">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<h4 class="text-900 font-semibold flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-info-circle text-blue-500"></i>
|
||||
Informations Requises
|
||||
</h4>
|
||||
<ul class="text-700 line-height-3 mb-4">
|
||||
<li><strong>Nom d'utilisateur</strong> : Identifiant unique (3-50 caractères)</li>
|
||||
<li><strong>Email</strong> : Adresse email valide</li>
|
||||
<li><strong>Mot de passe</strong> : Au moins 8 caractères</li>
|
||||
</ul>
|
||||
|
||||
<!-- Section Sécurité -->
|
||||
<div>
|
||||
<div class="flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-shield text-purple-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Recommandations de Sécurité</h5>
|
||||
</div>
|
||||
<div class="surface-50 border-round p-3">
|
||||
<ul class="text-700 line-height-3 m-0 pl-3">
|
||||
<li class="mb-2">Utilisez un mot de passe fort avec majuscules, minuscules, chiffres et symboles</li>
|
||||
<li class="mb-2">Évitez les mots de passe trop simples ou courants</li>
|
||||
<li class="mb-2">Le mot de passe sera hashé et sécurisé par Keycloak</li>
|
||||
<li>L'utilisateur pourra modifier son mot de passe après connexion</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="text-900 font-semibold flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-shield text-purple-500"></i>
|
||||
Sécurité
|
||||
</h4>
|
||||
<ul class="text-700 line-height-3 mb-4">
|
||||
<li>Le mot de passe doit contenir au moins 8 caractères</li>
|
||||
<li>Utilisez une combinaison de lettres, chiffres et caractères spéciaux</li>
|
||||
<li>L'utilisateur pourra modifier son mot de passe après la première connexion</li>
|
||||
</ul>
|
||||
|
||||
<!-- Section Configuration -->
|
||||
<div>
|
||||
<div class="flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-cog text-orange-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Options de Configuration</h5>
|
||||
</div>
|
||||
<div class="surface-50 border-round p-3">
|
||||
<ul class="text-700 line-height-3 m-0 pl-3">
|
||||
<li class="mb-2">
|
||||
<strong>Compte activé</strong> : Si coché, l'utilisateur peut se connecter immédiatement.
|
||||
</li>
|
||||
<li class="mb-2">
|
||||
<strong>Email vérifié</strong> : Si coché, l'email est considéré comme vérifié (pas de vérification requise).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Realm</strong> : lions-user-manager est le realm par défaut pour la gestion des utilisateurs.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h4 class="text-900 font-semibold flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-cog text-orange-500"></i>
|
||||
Configuration
|
||||
</h4>
|
||||
<ul class="text-700 line-height-3 mb-0">
|
||||
<li><strong>Compte activé</strong> : L'utilisateur peut se connecter immédiatement</li>
|
||||
<li><strong>Email vérifié</strong> : L'email est considéré comme vérifié</li>
|
||||
<li><strong>Realm</strong> : Espace d'administration Keycloak</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<f:facet name="footer">
|
||||
<div class="flex justify-content-end">
|
||||
<p:commandButton value="Fermer"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-text"
|
||||
onclick="PF('helpDialog').hide();"
|
||||
type="button" />
|
||||
</div>
|
||||
<p:commandButton value="Fermer" icon="pi pi-times" styleClass="p-button-text"
|
||||
onclick="PF('helpDialog').hide();" type="button" />
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
</ui:composition>
|
||||
@@ -1,314 +1,49 @@
|
||||
<!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">
|
||||
<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" template="/templates/main-template.xhtml">
|
||||
|
||||
<f:metadata>
|
||||
<f:viewParam name="userId" value="#{userProfilBean.userId}" />
|
||||
<f:viewParam name="realm" value="#{userProfilBean.realmName}" />
|
||||
<f:event type="preRenderView" listener="#{userProfilBean.loadUser}" />
|
||||
</f:metadata>
|
||||
|
||||
<ui:param name="page" value="#{userProfilBean}"/>
|
||||
<ui:param name="page" value="#{userProfilBean}" />
|
||||
<ui:define name="title">Modifier Utilisateur - 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-pencil text-warning-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Modifier Utilisateur</h3>
|
||||
<p class="text-600 m-0">Modifier les informations d'un utilisateur existant dans Keycloak</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<h:link outcome="/pages/user-manager/users/view" styleClass="p-button p-button-text">
|
||||
<f:param name="userId" value="#{userProfilBean.userId}" />
|
||||
<f:param name="realm" value="#{userProfilBean.realmName}" />
|
||||
<i class="pi pi-eye mr-2"></i>
|
||||
Voir le profil
|
||||
</h:link>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- En-tête -->
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-pencil text-warning-500" />
|
||||
<ui:param name="title" value="Modifier Utilisateur" />
|
||||
<ui:param name="description" value="Modifier les informations de l'utilisateur et gérer ses rôles" />
|
||||
</ui:include>
|
||||
|
||||
<!-- ================================================================
|
||||
FORMULAIRE D'ÉDITION
|
||||
================================================================ -->
|
||||
<h:form id="formUserEdit">
|
||||
<!-- Messages globaux -->
|
||||
<div class="col-12">
|
||||
<fr:growl id="formMessages" />
|
||||
</div>
|
||||
<div class="card">
|
||||
<p:tabView id="userTabs">
|
||||
<p:tab title="Profil Utilisateur">
|
||||
<ui:include src="/templates/components/user-management/user-form.xhtml">
|
||||
<ui:param name="user" value="#{userProfilBean.user}" />
|
||||
<ui:param name="mode" value="edit" />
|
||||
<ui:param name="showPasswordFields" value="false" />
|
||||
<ui:param name="submitAction" value="#{userProfilBean.updateUser}" />
|
||||
<ui:param name="cancelOutcome" value="/pages/user-manager/users/list" />
|
||||
<ui:param name="useParentForm" value="false" />
|
||||
</ui:include>
|
||||
</p:tab>
|
||||
|
||||
<h:panelGroup rendered="#{userProfilBean.user != null}">
|
||||
<!-- Informations de Base -->
|
||||
<div class="col-12 lg:col-8">
|
||||
<div class="grid">
|
||||
<!-- Section Informations de Base -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center gap-2 mb-4">
|
||||
<i class="pi pi-user text-blue-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Informations de Base</h5>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Nom d'utilisateur -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Nom d'utilisateur"
|
||||
value="#{userProfilBean.user.username}"
|
||||
required="true"
|
||||
placeholder="ex: jdupont"
|
||||
disabled="true"
|
||||
helpText="Le nom d'utilisateur ne peut pas être modifié">
|
||||
<f:validateLength for="input" minimum="3" maximum="50" />
|
||||
</fr:fieldInput>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Adresse email"
|
||||
value="#{userProfilBean.user.email}"
|
||||
required="true"
|
||||
type="email"
|
||||
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>
|
||||
|
||||
<!-- Prénom -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Prénom"
|
||||
value="#{userProfilBean.user.prenom}"
|
||||
required="true"
|
||||
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>
|
||||
|
||||
<!-- Nom -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Nom"
|
||||
value="#{userProfilBean.user.nom}"
|
||||
required="true"
|
||||
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>
|
||||
|
||||
<!-- Téléphone (optionnel) -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Téléphone"
|
||||
value="#{userProfilBean.user.telephone}"
|
||||
placeholder="ex: +33 6 12 34 56 78"
|
||||
helpText="Numéro de téléphone (optionnel)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section Configuration -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center gap-2 mb-4">
|
||||
<i class="pi pi-cog text-purple-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Configuration</h5>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Realm (lecture seule) -->
|
||||
<div class="col-12 md:col-6">
|
||||
<fr:fieldInput
|
||||
label="Realm Keycloak"
|
||||
value="#{userProfilBean.realmName}"
|
||||
disabled="true"
|
||||
helpText="Le realm ne peut pas être modifié" />
|
||||
</div>
|
||||
|
||||
<!-- Options -->
|
||||
<div class="col-12 md:col-6">
|
||||
<label class="block text-900 font-medium mb-2">
|
||||
<i class="pi pi-check-square text-500 mr-1"></i>
|
||||
Options
|
||||
</label>
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex flex-column gap-3">
|
||||
<!-- Compte activé -->
|
||||
<fr:fieldCheckbox id="enabled"
|
||||
label="Compte activé"
|
||||
value="#{userProfilBean.user.enabled}"
|
||||
helpText="L'utilisateur peut se connecter" />
|
||||
|
||||
<!-- Email vérifié -->
|
||||
<fr:fieldCheckbox id="emailVerified"
|
||||
label="Email vérifié"
|
||||
value="#{userProfilBean.user.emailVerified}"
|
||||
helpText="Marquer l'email comme vérifié" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
APERÇU ET RÉSUMÉ
|
||||
================================================================ -->
|
||||
<div class="col-12 lg:col-4">
|
||||
<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 <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);">
|
||||
<h:outputText value="#{userProfilBean.user.prenom != null ? userProfilBean.user.prenom.substring(0,1).toUpperCase() : 'U'}#{userProfilBean.user.nom != null ? userProfilBean.user.nom.substring(0,1).toUpperCase() : 'U'}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Résumé des informations -->
|
||||
<div class="flex flex-column gap-3">
|
||||
<!-- Username -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Nom d'utilisateur</small>
|
||||
<div class="font-semibold text-900">
|
||||
<h:outputText value="#{userProfilBean.user.username}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nom complet -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Nom complet</small>
|
||||
<div class="font-semibold text-900">
|
||||
<h:outputText value="#{userProfilBean.user.prenom} #{userProfilBean.user.nom}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Email</small>
|
||||
<div class="font-semibold text-900" style="word-break: break-all;">
|
||||
<h:outputText value="#{userProfilBean.user.email}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statut -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Statut</small>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<fr:tag value="#{userProfilBean.user.enabled ? 'Activé' : 'Désactivé'}"
|
||||
severity="#{userProfilBean.user.enabled ? 'success' : 'secondary'}" />
|
||||
<fr:tag value="#{userProfilBean.user.emailVerified ? 'Email vérifié' : 'Email non vérifié'}"
|
||||
severity="#{userProfilBean.user.emailVerified ? 'info' : 'warning'}"
|
||||
styleClass="text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Realm -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<small class="text-500 block mb-1">Realm</small>
|
||||
<div class="font-semibold text-900">
|
||||
<i class="pi pi-globe text-purple-500 mr-1"></i>
|
||||
#{userProfilBean.realmName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</h:panelGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
ACTIONS
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex flex-wrap gap-2 align-items-center justify-content-between">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<!-- Bouton Enregistrer -->
|
||||
<fr:commandButton value="Enregistrer les modifications"
|
||||
icon="pi pi-check"
|
||||
severity="success"
|
||||
action="#{userProfilBean.updateUser}"
|
||||
update=":formUserEdit"
|
||||
validateClient="true" />
|
||||
|
||||
<!-- Bouton Annuler -->
|
||||
<fr:commandButton value="Annuler"
|
||||
icon="pi pi-times"
|
||||
outlined="true"
|
||||
outcome="/pages/user-manager/users/view"
|
||||
immediate="true">
|
||||
<f:param name="userId" value="#{userProfilBean.userId}" />
|
||||
<f:param name="realm" value="#{userProfilBean.realmName}" />
|
||||
</fr:commandButton>
|
||||
</div>
|
||||
|
||||
<!-- Info champs requis -->
|
||||
<div class="flex align-items-center gap-2 text-500 text-sm">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span><span class="text-red-500">*</span> Champs obligatoires</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
<!-- Message si utilisateur non trouvé -->
|
||||
<h:panelGroup rendered="#{userProfilBean.user == null}">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="text-center p-5">
|
||||
<i class="pi pi-exclamation-triangle text-orange-500" style="font-size: 3rem"></i>
|
||||
<h3 class="text-900 font-semibold mt-3 mb-2">Utilisateur non trouvé</h3>
|
||||
<p class="text-600">L'utilisateur demandé n'existe pas ou n'a pas pu être chargé.</p>
|
||||
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button mt-3">
|
||||
<i class="pi pi-arrow-left mr-2"></i>
|
||||
Retour à la liste
|
||||
</h:link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
</h:form>
|
||||
<p:tab title="Gestion des Rôles" rendered="#{not empty userProfilBean.user.id}">
|
||||
<h:form id="rolesForm">
|
||||
<ui:include src="/templates/components/user-management/user-roles-manager.xhtml">
|
||||
<ui:param name="user" value="#{userProfilBean.user}" />
|
||||
<ui:param name="realm" value="#{userProfilBean.realmName}" />
|
||||
<ui:param name="roleBean" value="#{roleGestionBean}" />
|
||||
</ui:include>
|
||||
</h:form>
|
||||
</p:tab>
|
||||
</p:tabView>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION (Freya Extension)
|
||||
================================================================ -->
|
||||
<!-- Suppression désactivée - utiliser la page liste pour supprimer des utilisateurs -->
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
</ui:composition>
|
||||
@@ -1,13 +1,9 @@
|
||||
<!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">
|
||||
<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" template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{userListBean}"/>
|
||||
<ui:param name="page" value="#{userListBean}" />
|
||||
<ui:define name="title">Liste des Utilisateurs - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
@@ -23,37 +19,15 @@
|
||||
<i class="pi pi-users text-blue-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Gestion des Utilisateurs</h3>
|
||||
<p class="text-600 m-0">Gestion centralisée des utilisateurs Keycloak - Recherche, création, modification et suppression</p>
|
||||
<p class="text-600 m-0">Gestion centralisée des utilisateurs Keycloak - Recherche,
|
||||
création, modification et suppression</p>
|
||||
</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" />
|
||||
<fr:commandButton
|
||||
value="Nouvel Utilisateur"
|
||||
icon="pi pi-user-plus"
|
||||
severity="success"
|
||||
outcome="/pages/user-manager/users/create" />
|
||||
<p:commandButton value="Rafraîchir" icon="pi pi-refresh" styleClass="p-button-secondary"
|
||||
action="#{userListBean.refreshData}" update=":formUserList" process="@this" />
|
||||
<p:commandButton value="Nouvel Utilisateur" icon="pi pi-user-plus"
|
||||
styleClass="p-button-success" outcome="/pages/user-manager/users/create" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,7 +49,7 @@
|
||||
<div class="text-900 font-bold text-2xl">#{userListBean.totalRecords}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-users text-blue-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,7 +69,7 @@
|
||||
<div class="text-900 font-bold text-2xl">#{userListBean.activeUsersCount}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-green-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-check-circle text-green-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,10 +80,8 @@
|
||||
</span>
|
||||
<span class="text-500 text-sm">Taux d'activation</span>
|
||||
</div>
|
||||
<p:progressBar value="#{userListBean.activeUsersPercentage}"
|
||||
styleClass="mt-2"
|
||||
style="height: 4px"
|
||||
showValue="false" />
|
||||
<p:progressBar value="#{userListBean.activeUsersPercentage}" styleClass="mt-2"
|
||||
style="height: 4px" showValue="false" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -122,7 +94,7 @@
|
||||
<div class="text-900 font-bold text-2xl">#{userListBean.disabledUsersCount}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-red-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-times-circle text-red-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -133,10 +105,8 @@
|
||||
</span>
|
||||
<span class="text-500 text-sm">Taux de désactivation</span>
|
||||
</div>
|
||||
<p:progressBar value="#{userListBean.disabledUsersPercentage}"
|
||||
styleClass="mt-2"
|
||||
style="height: 4px"
|
||||
showValue="false" />
|
||||
<p:progressBar value="#{userListBean.disabledUsersPercentage}" styleClass="mt-2"
|
||||
style="height: 4px" showValue="false" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -146,10 +116,11 @@
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div style="max-width: 150px;">
|
||||
<div class="text-500 font-medium mb-1">Realm Actuel</div>
|
||||
<div class="text-900 font-bold text-xl" style="word-break: break-word;">#{userListBean.realmName}</div>
|
||||
<div class="text-900 font-bold text-xl" style="word-break: break-word;">
|
||||
#{userListBean.realmName}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-globe text-purple-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,422 +135,124 @@
|
||||
SECTION RECHERCHE ET FILTRES
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<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="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>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<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>
|
||||
<label for="searchText" class="block text-900 font-medium mb-2">Recherche</label>
|
||||
<p:inputText id="searchText" value="#{userListBean.searchText}"
|
||||
placeholder="Nom, email..." styleClass="w-full">
|
||||
<p:ajax event="keyup" delay="500" update=":formUserList:userTable"
|
||||
listener="#{userListBean.search}" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<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>
|
||||
<label for="realmSelect" class="block text-900 font-medium mb-2">Realm</label>
|
||||
<p:selectOneMenu id="realmSelect" value="#{userListBean.realmName}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="lions-user-manager" itemValue="lions-user-manager" />
|
||||
<f:selectItem itemLabel="master" itemValue="master" />
|
||||
<p:ajax update=":formUserList:userTable" listener="#{userListBean.search}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<fr:fieldSelect id="statutFilter"
|
||||
label="Statut"
|
||||
value="#{userListBean.selectedStatut}">
|
||||
<label for="statusSelect" class="block text-900 font-medium mb-2">Statut</label>
|
||||
<p:selectOneMenu id="statusSelect" value="#{userListBean.selectedStatut}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous" itemValue="#{null}" />
|
||||
<f:selectItems value="#{userListBean.statutOptions}" />
|
||||
<p:ajax event="change"
|
||||
update=":formUserList:userTable"
|
||||
listener="#{userListBean.search}" />
|
||||
</fr:fieldSelect>
|
||||
<p:ajax update=":formUserList:userTable" listener="#{userListBean.search}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<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"
|
||||
rendered="#{userListBean.searchText != null or userListBean.selectedStatut != null}" />
|
||||
<div class="col-12 lg:col-2 flex align-items-end">
|
||||
<p:commandButton value="Réinitialiser" icon="pi pi-refresh"
|
||||
styleClass="p-button-secondary w-full" action="#{userListBean.resetSearch}"
|
||||
update=":formUserList:userTable @form" />
|
||||
</div>
|
||||
</div>
|
||||
</fr:panel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
TABLEAU DES UTILISATEURS
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<ui:include src="/templates/components/shared/tables/user-data-table.xhtml">
|
||||
<ui:param name="users" value="#{userListBean.users}" />
|
||||
<ui:param name="headerTitle" value="Liste des Utilisateurs" />
|
||||
<ui:param name="tableId" value="userTable" />
|
||||
<ui:param name="paginator" value="true" />
|
||||
<ui:param name="rows" value="#{userListBean.pageSize}" />
|
||||
<ui:param name="showActions" value="true" />
|
||||
<ui:param name="showSelection" value="false" />
|
||||
<ui:param name="lazy" value="true" />
|
||||
<ui:param name="totalRecords" value="#{userListBean.totalRecords}" />
|
||||
<ui:param name="actionBean" value="#{userListBean}" />
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
ACTIONS RAPIDES
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-list text-blue-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Liste des Utilisateurs</h5>
|
||||
</div>
|
||||
<fr:tag value="#{userListBean.totalRecords} utilisateur(s)"
|
||||
severity="info"
|
||||
icon="pi pi-users" />
|
||||
<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>
|
||||
|
||||
<p:growl id="formMessages" />
|
||||
|
||||
<p:dataTable
|
||||
id="userTable"
|
||||
value="#{userListBean.users}"
|
||||
var="user"
|
||||
rowKey="#{user.id}"
|
||||
paginator="true"
|
||||
rows="#{userListBean.pageSize != null ? userListBean.pageSize : 25}"
|
||||
rowsPerPageTemplate="10,25,50,100"
|
||||
emptyMessage="Aucun utilisateur trouvé"
|
||||
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" 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;">
|
||||
<span class="font-bold">
|
||||
#{user.prenom != null ? user.prenom.substring(0,1).toUpperCase() : 'U'}#{user.nom != null ? user.nom.substring(0,1).toUpperCase() : 'U'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex flex-column">
|
||||
<span class="font-semibold text-900">#{user.username}</span>
|
||||
<span class="text-600 text-sm">#{user.prenom} #{user.nom}</span>
|
||||
</div>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Email -->
|
||||
<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>
|
||||
<p:outputPanel rendered="#{user.emailVerified}">
|
||||
<i class="pi pi-check-circle text-green-500" title="Email vérifié"></i>
|
||||
</p:outputPanel>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Statut -->
|
||||
<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" 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}" />
|
||||
|
||||
<ui:fragment rendered="#{user.realmRoles != null and user.realmRoles.size() > 0}">
|
||||
<ui:repeat value="#{user.realmRoles}" var="role" varStatus="status">
|
||||
<p:tag value="#{role}" severity="info"
|
||||
rendered="#{status.index lt 3}"
|
||||
styleClass="mr-1" />
|
||||
</ui:repeat>
|
||||
<p:tag value="+#{user.realmRoles.size() - 3}" severity="secondary"
|
||||
rendered="#{user.realmRoles.size() > 3}" />
|
||||
</ui:fragment>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Actions -->
|
||||
<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-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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<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>
|
||||
|
||||
<p:divider />
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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 class="grid">
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<p:commandButton value="Créer un Utilisateur" icon="pi pi-user-plus"
|
||||
styleClass="w-full p-button-success" outcome="/pages/user-manager/users/create" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<p:commandButton value="Exporter la Liste" icon="pi pi-download"
|
||||
styleClass="w-full p-button-secondary" action="#{userListBean.exportToCSV}"
|
||||
ajax="false" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<p:commandButton value="Importer des Utilisateurs" icon="pi pi-upload"
|
||||
styleClass="w-full p-button-info" onclick="PF('importUsersDialog').show()"
|
||||
type="button" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<p:commandButton value="Gestion des Rôles" icon="pi pi-shield"
|
||||
styleClass="w-full p-button-primary" outcome="/pages/user-manager/roles/list" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG D'IMPORT CSV (Freya formDialog)
|
||||
DIALOG D'IMPORT
|
||||
================================================================ -->
|
||||
<p:dialog widgetVar="importUsersDialog"
|
||||
header="Importer des Utilisateurs depuis CSV"
|
||||
modal="true"
|
||||
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>
|
||||
<p:dialog id="importUsersDialog" widgetVar="importUsersDialog" header="Importer des Utilisateurs" 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">
|
||||
<p:commandButton value="Annuler" icon="pi pi-times" styleClass="p-button-secondary"
|
||||
onclick="PF('importUsersDialog').hide()" type="button" />
|
||||
<p:commandButton value="Importer" icon="pi pi-upload" styleClass="p-button-success"
|
||||
action="#{userListBean.importUsers}" update=":formUserList"
|
||||
oncomplete="PF('importUsersDialog').hide()" />
|
||||
</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>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION GLOBAL
|
||||
================================================================ -->
|
||||
<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>
|
||||
</ui:composition>
|
||||
@@ -4,7 +4,6 @@
|
||||
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">
|
||||
|
||||
@@ -121,7 +120,7 @@
|
||||
<label class="block text-600 font-medium mb-2 text-sm">Rôles assignés</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<ui:repeat value="#{userSessionBean.roles}" var="role">
|
||||
<fr:tag value="#{role}" severity="info" styleClass="text-sm" />
|
||||
<p:badge value="#{role}" severity="info" styleClass="text-sm"></p:badge>
|
||||
</ui:repeat>
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,9 +128,10 @@
|
||||
<div class="mb-3 pb-3 border-bottom-1 surface-border">
|
||||
<label class="block text-600 font-medium mb-2 text-sm">Rôle principal</label>
|
||||
<div class="flex align-items-center">
|
||||
<fr:tag value="#{userSessionBean.primaryRole}"
|
||||
severity="success"
|
||||
styleClass="text-sm" />
|
||||
<p:badge value="#{userSessionBean.primaryRole}"
|
||||
severity="success"
|
||||
styleClass="text-sm">
|
||||
</p:badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-2 text-sm">Statut du compte</label>
|
||||
<div class="flex align-items-center">
|
||||
<fr:tag value="Actif" severity="success" styleClass="text-sm" />
|
||||
<p:badge value="Actif" severity="success" styleClass="text-sm"></p:badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -198,7 +198,7 @@
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-2 text-sm">Token Type</label>
|
||||
<div class="flex align-items-center">
|
||||
<fr:tag value="Bearer" severity="info" styleClass="text-sm" />
|
||||
<p:badge value="Bearer" severity="info" styleClass="text-sm"></p:badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -333,29 +333,26 @@
|
||||
<span>Gestion du Profil</span>
|
||||
</h4>
|
||||
<div class="flex flex-column gap-2">
|
||||
<fr:commandButton value="Modifier mon profil"
|
||||
icon="pi pi-pencil"
|
||||
outlined="true"
|
||||
styleClass="w-full justify-content-start"
|
||||
disabled="true">
|
||||
<p:commandButton value="Modifier mon profil"
|
||||
icon="pi pi-pencil"
|
||||
styleClass="p-button-outlined w-full justify-content-start"
|
||||
disabled="true">
|
||||
<f:attribute name="data-tooltip" value="Fonctionnalité gérée par Keycloak"/>
|
||||
</fr:commandButton>
|
||||
</p:commandButton>
|
||||
|
||||
<fr:commandButton value="Changer mon mot de passe"
|
||||
icon="pi pi-key"
|
||||
outlined="true"
|
||||
styleClass="w-full justify-content-start"
|
||||
disabled="true">
|
||||
<p:commandButton value="Changer mon mot de passe"
|
||||
icon="pi pi-key"
|
||||
styleClass="p-button-outlined w-full justify-content-start"
|
||||
disabled="true">
|
||||
<f:attribute name="data-tooltip" value="Utilisez le portail Keycloak"/>
|
||||
</fr:commandButton>
|
||||
</p:commandButton>
|
||||
|
||||
<fr:commandButton value="Paramètres de sécurité"
|
||||
icon="pi pi-shield"
|
||||
outlined="true"
|
||||
styleClass="w-full justify-content-start"
|
||||
disabled="true">
|
||||
<p:commandButton value="Paramètres de sécurité"
|
||||
icon="pi pi-shield"
|
||||
styleClass="p-button-outlined w-full justify-content-start"
|
||||
disabled="true">
|
||||
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
|
||||
</fr:commandButton>
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -368,33 +365,28 @@
|
||||
<span>Sessions et Sécurité</span>
|
||||
</h4>
|
||||
<div class="flex flex-column gap-2">
|
||||
<fr:commandButton value="Voir mes sessions actives"
|
||||
icon="pi pi-desktop"
|
||||
outlined="true"
|
||||
severity="info"
|
||||
styleClass="w-full justify-content-start"
|
||||
disabled="true">
|
||||
<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">
|
||||
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
|
||||
</fr:commandButton>
|
||||
</p:commandButton>
|
||||
|
||||
<fr:commandButton value="Historique des connexions"
|
||||
icon="pi pi-history"
|
||||
outlined="true"
|
||||
severity="secondary"
|
||||
styleClass="w-full justify-content-start"
|
||||
disabled="true">
|
||||
<p:commandButton value="Historique des connexions"
|
||||
icon="pi pi-history"
|
||||
styleClass="p-button-outlined p-button-secondary w-full justify-content-start"
|
||||
disabled="true">
|
||||
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
|
||||
</fr:commandButton>
|
||||
</p:commandButton>
|
||||
|
||||
<fr:commandButton value="Se déconnecter"
|
||||
icon="pi pi-sign-out"
|
||||
severity="danger"
|
||||
styleClass="w-full justify-content-start"
|
||||
action="#{userSessionBean.logout}">
|
||||
<p:commandButton value="Se déconnecter"
|
||||
icon="pi pi-sign-out"
|
||||
styleClass="p-button-danger 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" />
|
||||
</fr:commandButton>
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -405,19 +397,16 @@
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION (Freya Extension)
|
||||
DIALOG DE CONFIRMATION
|
||||
================================================================ -->
|
||||
<!-- 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">
|
||||
<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: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>
|
||||
|
||||
<!-- Animation CSS pour le badge "Connecté" -->
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
<!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"
|
||||
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
template="/templates/main-template.xhtml">
|
||||
<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"
|
||||
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core" 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:param name="page" value="#{userProfilBean}" />
|
||||
<ui:define name="title">Profil Utilisateur - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
@@ -40,135 +36,31 @@
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
CARTE PROFIL PRINCIPAL
|
||||
CARTE PROFIL PRINCIPAL & ACTIONS
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h:panelGroup rendered="#{userProfilBean.user != null}">
|
||||
<div class="grid">
|
||||
<!-- Photo de profil et informations principales -->
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="text-center mb-4">
|
||||
<!-- Avatar avec gradient -->
|
||||
<div style="width: 140px; height: 140px; 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 1.5rem auto; font-size: 3.5rem; font-weight: bold; color: white; box-shadow: 0 8px 24px rgba(0,0,0,0.12);">
|
||||
#{userProfilBean.user.prenom != null ? userProfilBean.user.prenom.substring(0,1).toUpperCase() : 'U'}#{userProfilBean.user.nom != null ? userProfilBean.user.nom.substring(0,1).toUpperCase() : 'U'}
|
||||
</div>
|
||||
<ui:fragment rendered="#{userProfilBean.user != null}">
|
||||
<div class="col-12">
|
||||
<div class="card p-0 overflow-hidden">
|
||||
<ui:include src="/templates/components/user-management/user-card.xhtml">
|
||||
<ui:param name="user" value="#{userProfilBean.user}" />
|
||||
<ui:param name="showActions" value="true" />
|
||||
<ui:param name="layout" value="horizontal" />
|
||||
<ui:param name="showOrganisation" value="true" />
|
||||
<ui:param name="showRoles" value="true" />
|
||||
<ui:param name="clickable" value="false" />
|
||||
<ui:param name="actionBean" value="#{userProfilBean}" />
|
||||
<ui:param name="showEdit" value="true" />
|
||||
<ui:param name="editOutcome" value="/pages/user-manager/users/edit" />
|
||||
<ui:param name="hasDeleteAction" value="true" />
|
||||
<ui:param name="deleteAction" value="#{userProfilBean.deleteUser}" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
</ui:fragment>
|
||||
|
||||
<!-- Nom complet -->
|
||||
<h3 class="text-900 font-semibold text-2xl mb-2">#{userProfilBean.user.prenom} #{userProfilBean.user.nom}</h3>
|
||||
|
||||
<!-- Email -->
|
||||
<p class="text-600 mb-3 flex align-items-center justify-content-center gap-2">
|
||||
<i class="pi pi-envelope"></i>
|
||||
#{userProfilBean.user.email}
|
||||
</p>
|
||||
|
||||
<!-- Badge de statut -->
|
||||
<div class="inline-flex align-items-center justify-content-center gap-2">
|
||||
<fr:tag value="#{userProfilBean.user.enabled ? 'ACTIF' : 'INACTIF'}"
|
||||
severity="#{userProfilBean.user.enabled ? 'success' : 'danger'}"
|
||||
styleClass="text-sm" />
|
||||
</div>
|
||||
|
||||
<!-- Badge email vérifié -->
|
||||
<div class="mt-2 flex justify-content-center">
|
||||
<fr:tag value="Email vérifié"
|
||||
severity="success"
|
||||
rendered="#{userProfilBean.user.emailVerified}"
|
||||
styleClass="text-xs" />
|
||||
<fr:tag value="Email non vérifié"
|
||||
severity="warning"
|
||||
rendered="#{not userProfilBean.user.emailVerified}"
|
||||
styleClass="text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Informations détaillées -->
|
||||
<div class="col-12 md:col-8">
|
||||
<div class="grid">
|
||||
<!-- Colonne gauche: Informations personnelles -->
|
||||
<div class="col-12 md:col-6">
|
||||
<h4 class="text-900 font-semibold text-lg mb-3 flex align-items-center gap-2">
|
||||
<i class="pi pi-user text-blue-500"></i>
|
||||
Informations Personnelles
|
||||
</h4>
|
||||
|
||||
<div class="mb-3 pb-3 border-bottom-1 surface-border">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Nom d'utilisateur</label>
|
||||
<p class="text-900 font-semibold m-0">#{userProfilBean.user.username}</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 pb-3 border-bottom-1 surface-border">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Nom complet</label>
|
||||
<p class="text-900 font-semibold m-0">#{userProfilBean.user.prenom} #{userProfilBean.user.nom}</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 pb-3 border-bottom-1 surface-border">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Adresse email</label>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<p class="text-900 font-semibold m-0">#{userProfilBean.user.email}</p>
|
||||
<i class="pi pi-check-circle text-green-500"
|
||||
rendered="#{userProfilBean.user.emailVerified}"
|
||||
title="Email vérifié"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Prénom</label>
|
||||
<p class="text-900 font-semibold m-0">#{userProfilBean.user.prenom}</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Nom</label>
|
||||
<p class="text-900 font-semibold m-0">#{userProfilBean.user.nom}</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" rendered="#{userProfilBean.user.telephone != null}">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Téléphone</label>
|
||||
<p class="text-900 font-semibold m-0">#{userProfilBean.user.telephone}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Colonne droite: Rôles et permissions -->
|
||||
<div class="col-12 md:col-6">
|
||||
<h4 class="text-900 font-semibold text-lg mb-3 flex align-items-center gap-2">
|
||||
<i class="pi pi-shield text-purple-500"></i>
|
||||
Rôles et Permissions
|
||||
</h4>
|
||||
|
||||
<div class="mb-3 pb-3 border-bottom-1 surface-border">
|
||||
<label class="block text-600 font-medium mb-2 text-sm">Rôles Realm assignés</label>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<h:outputText value="Aucun rôle"
|
||||
styleClass="text-500 text-sm"
|
||||
rendered="#{userProfilBean.user.realmRoles == null or userProfilBean.user.realmRoles.size() == 0}" />
|
||||
<ui:repeat value="#{userProfilBean.user.realmRoles}" var="role">
|
||||
<fr:tag value="#{role}" severity="info" styleClass="text-sm" />
|
||||
</ui:repeat>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<fr:tag value="#{userProfilBean.user.enabled ? 'ACTIF' : 'INACTIF'}"
|
||||
severity="#{userProfilBean.user.enabled ? 'success' : 'danger'}"
|
||||
styleClass="text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Realm</label>
|
||||
<p class="text-900 font-semibold m-0">#{userProfilBean.realmName}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
<h:panelGroup rendered="#{userProfilBean.user == null}">
|
||||
<ui:fragment rendered="#{userProfilBean.user == null}">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="text-center p-5">
|
||||
<i class="pi pi-exclamation-triangle text-orange-500" style="font-size: 3rem"></i>
|
||||
<h3 class="text-900 font-semibold mt-3 mb-2">Utilisateur non trouvé</h3>
|
||||
@@ -178,55 +70,10 @@
|
||||
Retour à la liste
|
||||
</h:link>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
ACTIONS
|
||||
================================================================ -->
|
||||
<div class="col-12" rendered="#{userProfilBean.user != null}">
|
||||
<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="formUserActions">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-4">
|
||||
<fr:commandButton value="Modifier"
|
||||
icon="pi pi-pencil"
|
||||
severity="primary"
|
||||
styleClass="w-full"
|
||||
outcome="/pages/user-manager/users/edit">
|
||||
<f:param name="userId" value="#{userProfilBean.userId}" />
|
||||
<f:param name="realm" value="#{userProfilBean.realmName}" />
|
||||
</fr:commandButton>
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<fr:commandButton value="Gérer les Rôles"
|
||||
icon="pi pi-key"
|
||||
severity="help"
|
||||
styleClass="w-full"
|
||||
outcome="/pages/user-manager/users/edit">
|
||||
<f:param name="userId" value="#{userProfilBean.userId}" />
|
||||
<f:param name="realm" value="#{userProfilBean.realmName}" />
|
||||
</fr:commandButton>
|
||||
</div>
|
||||
<div class="col-12 md:col-4">
|
||||
<fr:commandButton value="Retour à la liste"
|
||||
icon="pi pi-arrow-left"
|
||||
severity="secondary"
|
||||
styleClass="w-full"
|
||||
outcome="/pages/user-manager/users/list" />
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</ui:fragment>
|
||||
</div>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,21 +1,19 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Ligne de Log d'Audit (WOU/DRY Pattern)
|
||||
Composant réutilisable: Ligne de Log Audit - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Affiche une ligne de log d'audit avec informations détaillées
|
||||
Version: 2.0.0
|
||||
Description: Affiche une ligne de log d'audit avec icône de statut, informations
|
||||
principales et détails extensibles. Compatible avec les thèmes PrimeFaces.
|
||||
|
||||
Paramètres:
|
||||
- auditLog: AuditLogDTO (requis) - Le log d'audit à afficher
|
||||
- showDetails: Boolean (défaut: false) - Afficher les détails complets
|
||||
- showActions: Boolean (défaut: false) - Afficher les actions possibles
|
||||
- showDetails: Boolean (défaut: false) - Afficher les détails complets en ligne
|
||||
- showActions: Boolean (défaut: false) - Afficher les boutons d'action
|
||||
- compact: Boolean (défaut: false) - Mode compact
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
|
||||
@@ -26,85 +24,116 @@
|
||||
<ui:param name="auditLog" value="#{log}" />
|
||||
</ui:include>
|
||||
|
||||
2. Ligne avec détails:
|
||||
2. Ligne avec détails et actions:
|
||||
<ui:include src="/templates/components/audit/audit-log-row.xhtml">
|
||||
<ui:param name="auditLog" value="#{log}" />
|
||||
<ui:param name="showDetails" value="true" />
|
||||
<ui:param name="showActions" value="true" />
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
|
||||
<c:set var="showDetails" value="#{empty showDetails ? false : showDetails}" />
|
||||
<c:set var="showActions" value="#{empty showActions ? false : showActions}" />
|
||||
<c:set var="compact" value="#{empty compact ? false : compact}" />
|
||||
|
||||
<!-- Déterminer la severity selon le succès -->
|
||||
<c:set var="severity" value="#{auditLog.succes ? 'success' : 'danger'}" />
|
||||
<c:set var="icon" value="#{auditLog.succes ? 'pi-check-circle' : 'pi-times-circle'}" />
|
||||
|
||||
<div class="audit-log-row flex align-items-center gap-3 p-3 border-round surface-border border-1 #{styleClass}"
|
||||
style="#{compact ? 'padding: 0.5rem;' : ''}">
|
||||
|
||||
|
||||
<!-- Déterminer le style selon le succès/échec -->
|
||||
<c:set var="isSuccess" value="#{auditLog.succes}" />
|
||||
<c:set var="statusColor" value="#{isSuccess ? 'green' : 'red'}" />
|
||||
<c:set var="statusIcon" value="#{isSuccess ? 'pi-check-circle' : 'pi-times-circle'}" />
|
||||
<c:set var="statusLabel" value="#{isSuccess ? 'Succès' : 'Échec'}" />
|
||||
|
||||
<div
|
||||
class="audit-log-row flex align-items-start gap-3 p-3 border-round surface-border border-1 hover:surface-hover transition-all transition-duration-200 #{compact ? 'py-2' : ''} #{styleClass}">
|
||||
|
||||
<!-- Icône de statut -->
|
||||
<div class="flex align-items-center justify-content-center"
|
||||
style="width: 2.5rem; height: 2.5rem; border-radius: 50%; background: var(--#{severity}-100);">
|
||||
<i class="pi #{icon} text-#{severity}-600"></i>
|
||||
<div class="flex align-items-center justify-content-center border-round-lg flex-shrink-0"
|
||||
style="width: 2.5rem; height: 2.5rem; background-color: rgba(var(--#{statusColor}-500-rgb, 0,0,0), 0.1);">
|
||||
<i class="pi #{statusIcon} text-#{statusColor}-500"></i>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Informations principales -->
|
||||
<div class="flex-1">
|
||||
<div class="flex align-items-center gap-2 mb-1">
|
||||
<div class="flex align-items-center gap-2 mb-1 flex-wrap">
|
||||
<strong class="text-900">#{auditLog.typeAction}</strong>
|
||||
<p:tag
|
||||
value="#{auditLog.succes ? 'Succès' : 'Échec'}"
|
||||
severity="#{severity}"
|
||||
styleClass="text-xs" />
|
||||
<p:tag value="#{statusLabel}" severity="#{isSuccess ? 'success' : 'danger'}" styleClass="text-xs" />
|
||||
</div>
|
||||
|
||||
<div class="text-color-secondary text-sm">
|
||||
|
||||
<div class="flex align-items-center gap-3 text-color-secondary text-sm flex-wrap">
|
||||
<c:if test="#{not empty auditLog.acteurUsername}">
|
||||
<span><i class="pi pi-user mr-1"></i>#{auditLog.acteurUsername}</span>
|
||||
<span class="flex align-items-center gap-1">
|
||||
<i class="pi pi-user text-xs"></i>
|
||||
#{auditLog.acteurUsername}
|
||||
</span>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.ressourceType}">
|
||||
<span class="ml-3"><i class="pi pi-database mr-1"></i>#{auditLog.ressourceType}</span>
|
||||
<span class="flex align-items-center gap-1">
|
||||
<i class="pi pi-database text-xs"></i>
|
||||
#{auditLog.ressourceType}
|
||||
</span>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.dateAction}">
|
||||
<span class="ml-3"><i class="pi pi-calendar mr-1"></i>#{auditLog.dateAction}</span>
|
||||
<span class="flex align-items-center gap-1">
|
||||
<i class="pi pi-clock text-xs"></i>
|
||||
#{auditLog.dateAction}
|
||||
</span>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.realmName}">
|
||||
<span class="flex align-items-center gap-1">
|
||||
<i class="pi pi-globe text-xs"></i>
|
||||
#{auditLog.realmName}
|
||||
</span>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
<!-- Détails (si affichés) -->
|
||||
|
||||
<!-- Détails extensibles -->
|
||||
<c:if test="#{showDetails}">
|
||||
<div class="mt-2 text-color-secondary text-xs">
|
||||
<c:if test="#{not empty auditLog.ressourceId}">
|
||||
<div><strong>Ressource ID:</strong> #{auditLog.ressourceId}</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.details}">
|
||||
<div><strong>Détails:</strong> #{auditLog.details}</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.adresseIp}">
|
||||
<div><strong>IP:</strong> #{auditLog.adresseIp}</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.userAgent}">
|
||||
<div><strong>User Agent:</strong> #{auditLog.userAgent}</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.messageErreur}">
|
||||
<div class="text-red-600"><strong>Erreur:</strong> #{auditLog.messageErreur}</div>
|
||||
</c:if>
|
||||
<div class="mt-2 surface-100 border-round p-2">
|
||||
<div class="grid text-xs text-color-secondary">
|
||||
<c:if test="#{not empty auditLog.ressourceId}">
|
||||
<div class="col-12 md:col-6">
|
||||
<span class="font-semibold">Ressource ID: </span>
|
||||
<span class="text-900">#{auditLog.ressourceId}</span>
|
||||
</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.adresseIp}">
|
||||
<div class="col-12 md:col-6">
|
||||
<span class="font-semibold">Adresse IP: </span>
|
||||
<span class="text-900">#{auditLog.adresseIp}</span>
|
||||
</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.details}">
|
||||
<div class="col-12">
|
||||
<span class="font-semibold">Détails: </span>
|
||||
<span class="text-900">#{auditLog.details}</span>
|
||||
</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.userAgent}">
|
||||
<div class="col-12">
|
||||
<span class="font-semibold">User Agent: </span>
|
||||
<span class="text-900 text-overflow-ellipsis white-space-nowrap overflow-hidden"
|
||||
style="max-width: 300px;" title="#{auditLog.userAgent}">
|
||||
#{auditLog.userAgent}
|
||||
</span>
|
||||
</div>
|
||||
</c:if>
|
||||
<c:if test="#{not empty auditLog.messageErreur}">
|
||||
<div class="col-12">
|
||||
<span class="font-semibold text-red-600">Erreur: </span>
|
||||
<span class="text-red-700">#{auditLog.messageErreur}</span>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
<!-- Actions (si affichées) -->
|
||||
|
||||
<!-- Actions -->
|
||||
<c:if test="#{showActions}">
|
||||
<div class="flex gap-1">
|
||||
<p:commandButton
|
||||
icon="pi pi-eye"
|
||||
styleClass="p-button-text p-button-sm"
|
||||
title="Voir les détails"
|
||||
onclick="PF('auditLogDetailsDialog').show()" />
|
||||
<div class="flex gap-1 flex-shrink-0">
|
||||
<p:commandButton icon="pi pi-eye" styleClass="p-button-text p-button-sm p-button-rounded"
|
||||
title="Voir les détails" onclick="PF('auditLogDetailsDialog').show()" type="button" />
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,28 +1,31 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Carte Statistiques Audit (WOU/DRY Pattern)
|
||||
Composant réutilisable: Carte Statistiques Audit - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Affiche des statistiques d'audit dans une carte
|
||||
Version: 2.0.0
|
||||
Description: Wrapper spécialisé autour de kpi-card.xhtml pour le domaine Audit.
|
||||
Ajoute un contexte sémantique. Pour de nouvelles implémentations,
|
||||
utiliser directement kpi-card.xhtml.
|
||||
|
||||
Paramètres:
|
||||
- title: String (requis) - Titre de la carte
|
||||
- value: Number (requis) - Valeur à afficher
|
||||
- icon: String (requis) - Classe d'icône PrimeIcons
|
||||
- iconColor: String (requis) - Couleur de l'icône
|
||||
- icon: String (requis) - Classe d'icône PrimeIcons (sans préfixe "pi-")
|
||||
- iconColor: String (requis) - Couleur de l'icône (ex: "blue-600")
|
||||
- subtitle: String (optionnel) - Sous-titre
|
||||
- trend: Number (optionnel) - Tendance (pourcentage)
|
||||
- trendLabel: String (optionnel) - Libellé de la tendance
|
||||
- colSize: String (défaut: "col-12 md:col-6 lg:col-3") - Taille de colonne
|
||||
- clickable: Boolean (défaut: false) - Rendre la carte cliquable
|
||||
- clickAction: String (optionnel) - Action au clic
|
||||
- clickOutcome: String (optionnel) - Page de destination au clic
|
||||
- statusIcon: String (optionnel) - Icône de statut (déléguée à kpi-card)
|
||||
- statusLabel: String (optionnel) - Label de statut
|
||||
- statusValue: String (optionnel) - Valeur de statut
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
@@ -44,77 +47,25 @@
|
||||
<ui:param name="trendLabel" value="vs mois dernier" />
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
<c:set var="colSize" value="#{empty colSize ? 'col-12 md:col-6 lg:col-3' : colSize}" />
|
||||
<c:set var="clickable" value="#{empty clickable ? false : clickable}" />
|
||||
|
||||
<div class="field #{colSize}">
|
||||
<c:choose>
|
||||
<c:when test="#{clickable and not empty clickAction}">
|
||||
<p:commandLink
|
||||
styleClass="card-link"
|
||||
action="#{clickAction}">
|
||||
<div class="card surface-0 hover:surface-100 border-round-lg transition-all transition-duration-200 p-4">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">#{title}</span>
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi #{icon} text-#{iconColor} text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{value}</div>
|
||||
|
||||
<c:if test="#{not empty subtitle}">
|
||||
<div class="text-500 text-xs mb-2">#{subtitle}</div>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{not empty trend}">
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-arrow-#{trend >= 0 ? 'up' : 'down'} text-#{trend >= 0 ? 'green' : 'red'}-500 text-sm mr-2"></i>
|
||||
<span class="text-#{trend >= 0 ? 'green' : 'red'}-600 font-semibold text-sm mr-2">
|
||||
#{trend >= 0 ? '+' : ''}#{trend}%
|
||||
</span>
|
||||
<c:if test="#{not empty trendLabel}">
|
||||
<span class="text-500 text-xs">#{trendLabel}</span>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</p:commandLink>
|
||||
</c:when>
|
||||
|
||||
<c:otherwise>
|
||||
<div class="card surface-0 border-round-lg p-4">
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">#{title}</span>
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi #{icon} text-#{iconColor} text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{value}</div>
|
||||
|
||||
<c:if test="#{not empty subtitle}">
|
||||
<div class="text-500 text-xs mb-2">#{subtitle}</div>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{not empty trend}">
|
||||
<div class="flex align-items-center">
|
||||
<i class="pi pi-arrow-#{trend >= 0 ? 'up' : 'down'} text-#{trend >= 0 ? 'green' : 'red'}-500 text-sm mr-2"></i>
|
||||
<span class="text-#{trend >= 0 ? 'green' : 'red'}-600 font-semibold text-sm mr-2">
|
||||
#{trend >= 0 ? '+' : ''}#{trend}%
|
||||
</span>
|
||||
<c:if test="#{not empty trendLabel}">
|
||||
<span class="text-500 text-xs">#{trendLabel}</span>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
<!-- Déléguer à kpi-card.xhtml pour éviter la duplication de code (DRY) -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="#{title}" />
|
||||
<ui:param name="value" value="#{value}" />
|
||||
<ui:param name="icon" value="#{icon}" />
|
||||
<ui:param name="iconColor" value="#{iconColor}" />
|
||||
<ui:param name="subtitle" value="#{subtitle}" />
|
||||
<ui:param name="growthValue" value="#{trend}" />
|
||||
<ui:param name="growthLabel" value="#{trendLabel}" />
|
||||
<ui:param name="growthType" value="percentage" />
|
||||
<ui:param name="colSize" value="#{colSize}" />
|
||||
<ui:param name="clickable" value="#{clickable}" />
|
||||
<ui:param name="clickAction" value="#{clickAction}" />
|
||||
<ui:param name="clickOutcome" value="#{clickOutcome}" />
|
||||
<ui:param name="statusIcon" value="#{statusIcon}" />
|
||||
<ui:param name="statusLabel" value="#{statusLabel}" />
|
||||
<ui:param name="statusValue" value="#{statusValue}" />
|
||||
<ui:param name="styleClass" value="#{styleClass}" />
|
||||
</ui:include>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,9 +1,6 @@
|
||||
<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">
|
||||
<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">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Menu Navigation (WOU/DRY Pattern)
|
||||
@@ -13,51 +10,50 @@
|
||||
Description: Menu de navigation latéral pour Lions User Manager
|
||||
-->
|
||||
|
||||
<div class="menu-wrapper">
|
||||
<div class="sidebar-logo">
|
||||
<a href="/pages/user-manager/dashboard">
|
||||
<p:graphicImage name="images/logo-freya-single.svg" library="freya-layout" />
|
||||
</a>
|
||||
<a href="#" class="sidebar-pin" title="Toggle Menu">
|
||||
<span class="pin"></span>
|
||||
</a>
|
||||
<div class="menu-wrapper">
|
||||
<div class="sidebar-logo">
|
||||
<a href="/pages/user-manager/dashboard">
|
||||
<p:graphicImage name="images/logo-freya-single.svg" library="freya-layout" />
|
||||
</a>
|
||||
<a href="#" class="sidebar-pin" title="Toggle Menu">
|
||||
<span class="pin"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="layout-menu-container">
|
||||
<h:form id="menuform">
|
||||
<fr:menu widgetVar="FreyaMenuWidget">
|
||||
<!-- Dashboard -->
|
||||
<p:menuitem id="m_dashboard" value="Tableau de Bord" icon="pi pi-home"
|
||||
outcome="/pages/user-manager/dashboard" />
|
||||
|
||||
<!-- Gestion Utilisateurs -->
|
||||
<p:submenu id="m_users" label="Gestion Utilisateurs" icon="pi pi-users">
|
||||
<p:menuitem id="m_users_list" value="Liste des Utilisateurs" icon="pi pi-list"
|
||||
outcome="/pages/user-manager/users/list" />
|
||||
<p:menuitem id="m_users_create" value="Nouvel Utilisateur" icon="pi pi-user-plus"
|
||||
outcome="/pages/user-manager/users/create" />
|
||||
</p:submenu>
|
||||
|
||||
<!-- 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:submenu>
|
||||
|
||||
<!-- Audit -->
|
||||
<p:submenu id="m_audit" label="Audit" icon="pi pi-history">
|
||||
<p:menuitem id="m_audit_logs" value="Journal d'Audit" icon="pi pi-file-o"
|
||||
outcome="/pages/user-manager/audit/logs" />
|
||||
</p:submenu>
|
||||
|
||||
<!-- Synchronisation -->
|
||||
<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>
|
||||
</fr:menu>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-menu-container">
|
||||
<h:form id="menuform">
|
||||
<fr:menu widgetVar="FreyaMenuWidget">
|
||||
<!-- Dashboard -->
|
||||
<p:menuitem id="m_dashboard" value="Tableau de Bord" icon="pi pi-home" outcome="/pages/user-manager/dashboard" />
|
||||
|
||||
<!-- Gestion Utilisateurs -->
|
||||
<p:submenu id="m_users" label="Gestion Utilisateurs" icon="pi pi-users">
|
||||
<p:menuitem id="m_users_list" value="Liste des Utilisateurs" icon="pi pi-list" outcome="/pages/user-manager/users/list" />
|
||||
<p:menuitem id="m_users_create" value="Nouvel Utilisateur" icon="pi pi-user-plus" outcome="/pages/user-manager/users/create" />
|
||||
</p:submenu>
|
||||
|
||||
<!-- 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:submenu>
|
||||
|
||||
<!-- Audit -->
|
||||
<p:submenu id="m_audit" label="Audit" icon="pi pi-history">
|
||||
<p:menuitem id="m_audit_logs" value="Journal d'Audit" icon="pi pi-file-o" outcome="/pages/user-manager/audit/logs" />
|
||||
</p:submenu>
|
||||
|
||||
<!-- Synchronisation - 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')}">
|
||||
<p:menuitem id="m_admin_realm_assignments" value="Affectation Realms" icon="pi pi-sitemap" outcome="/pages/admin/realm-assignments" />
|
||||
</p:submenu>
|
||||
</fr:menu>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,52 +1,93 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant en-tête de page réutilisable (WOU/DRY Pattern)
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: En-tête de Page - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: En-tête de page avec icône, titre, description et actions
|
||||
Version: 2.0.0
|
||||
Description: En-tête de page avec icône, titre, description, fil d'Ariane et actions.
|
||||
Conforme au pattern WOU/DRY pour la cohérence visuelle de l'application.
|
||||
|
||||
Paramètres:
|
||||
- icon: String (optionnel) - Classe d'icône PrimeIcons (ex: "pi pi-users text-blue-500")
|
||||
- title: String (requis) - Titre de la page
|
||||
- description: String (optionnel) - Description de la page
|
||||
- breadcrumbParent: String (optionnel) - Libellé de la page parente (ex: "Utilisateurs")
|
||||
- breadcrumbParentLink: String (optionnel) - Lien de la page parente
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
|
||||
Usage:
|
||||
Slot facelet:
|
||||
- "actions": Contenu personnalisé pour les boutons d'action à droite
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
1. En-tête simple:
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-users text-blue-500" />
|
||||
<ui:param name="title" value="Gestion des Utilisateurs" />
|
||||
<ui:param name="description" value="Gestion centralisée des utilisateurs Keycloak" />
|
||||
</ui:include>
|
||||
|
||||
2. En-tête avec fil d'ariane et actions:
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-user-edit text-green-500" />
|
||||
<ui:param name="title" value="Créer un Utilisateur" />
|
||||
<ui:param name="description" value="Formulaire de création d'un nouvel utilisateur" />
|
||||
<ui:param name="breadcrumbParent" value="Utilisateurs" />
|
||||
<ui:param name="breadcrumbParentLink" value="/pages/user-manager/users/list" />
|
||||
<ui:define name="actions">
|
||||
Boutons d'action ici
|
||||
<p:commandButton value="Exporter" icon="pi pi-download" styleClass="p-button-secondary" />
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
|
||||
<div class="grid mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div>
|
||||
<h3 class="mb-2">
|
||||
<c:if test="#{not empty icon}">
|
||||
<i class="#{icon} mr-2"></i>
|
||||
<!-- Fil d'Ariane (Breadcrumb) -->
|
||||
<c:if test="#{not empty breadcrumbParent}">
|
||||
<div class="flex align-items-center gap-2 mb-2 text-sm">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty breadcrumbParentLink}">
|
||||
<h:link outcome="#{breadcrumbParentLink}"
|
||||
styleClass="text-primary no-underline hover:underline">
|
||||
#{breadcrumbParent}
|
||||
</h:link>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<span class="text-600">#{breadcrumbParent}</span>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<i class="pi pi-angle-right text-400 text-xs"></i>
|
||||
<span class="text-900 font-medium">#{title}</span>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<div class="card #{styleClass}">
|
||||
<div class="flex align-items-center justify-content-between flex-wrap gap-3">
|
||||
<!-- Partie gauche: Icône + Titre + Description -->
|
||||
<div class="flex align-items-center gap-3">
|
||||
<c:if test="#{not empty icon}">
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 3rem; height: 3rem;">
|
||||
<i class="#{icon} text-xl"></i>
|
||||
</div>
|
||||
</c:if>
|
||||
<div>
|
||||
<h3 class="m-0 text-900 font-bold">#{title}</h3>
|
||||
<c:if test="#{not empty description}">
|
||||
<p class="text-600 text-sm m-0 mt-1">#{description}</p>
|
||||
</c:if>
|
||||
#{title}
|
||||
</h3>
|
||||
<p class="text-600 m-0" rendered="#{not empty description}">#{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<!-- Partie droite: Actions -->
|
||||
<div class="flex align-items-center gap-2">
|
||||
<ui:insert name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,298 +1,117 @@
|
||||
<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">
|
||||
<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">
|
||||
|
||||
<!--
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ Lions User Manager - Elite Topbar (Freya Design) ║
|
||||
║ Real-time Session Monitor | Modern UI | Professional ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
Composant réutilisable: Topbar (WOU/DRY Pattern)
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Barre supérieure avec logo, menu et profil utilisateur
|
||||
-->
|
||||
|
||||
<h:outputStylesheet library="css" name="topbar-elite.css" />
|
||||
|
||||
<div class="layout-topbar lions-elite">
|
||||
<div class="layout-topbar">
|
||||
<div class="layout-topbar-wrapper">
|
||||
<!-- LEFT SECTION -->
|
||||
<div class="layout-topbar-left">
|
||||
<a href="#" class="menu-button">
|
||||
<i class="pi pi-bars" />
|
||||
<i class="pi pi-bars"/>
|
||||
</a>
|
||||
<h:link id="logolink" outcome="/pages/user-manager/dashboard" styleClass="layout-topbar-logo">
|
||||
<p:graphicImage
|
||||
name="images/#{guestPreferences.lightLogo ? 'logo-freya-white.svg' : 'logo-freya.svg'}"
|
||||
library="freya-layout" alt="Lions User Manager" title="Retour au tableau de bord" />
|
||||
<p:graphicImage name="images/#{guestPreferences.lightLogo ? 'logo-freya-white.svg' : 'logo-freya.svg'}" library="freya-layout" />
|
||||
</h:link>
|
||||
<span class="app-version">v2.0</span>
|
||||
</div>
|
||||
|
||||
<!-- CENTER - Menu -->
|
||||
<ui:include src="/templates/components/layout/menu.xhtml" />
|
||||
|
||||
<!-- RIGHT SECTION -->
|
||||
<div class="layout-topbar-right">
|
||||
<ul class="layout-topbar-actions">
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<li class="topbar-item">
|
||||
<a href="#" title="Actions rapides">
|
||||
<i class="topbar-icon pi pi-bolt" />
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Notifications -->
|
||||
<li class="topbar-item notifications-item">
|
||||
<a href="#" title="Notifications">
|
||||
<i class="topbar-icon pi pi-bell" />
|
||||
<span class="badge-count">5</span>
|
||||
</a>
|
||||
<ul class="notifications-dropdown">
|
||||
<li class="notif-header">
|
||||
<span class="font-semibold">Notifications</span>
|
||||
<span class="count-label">5 nouvelles</span>
|
||||
</li>
|
||||
<li class="divider" />
|
||||
<li class="notif-item">
|
||||
<i class="pi pi-user-plus text-blue-500" />
|
||||
<div>
|
||||
<div class="notif-title">Nouvel utilisateur</div>
|
||||
<div class="notif-time">Il y a 2 min</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="notif-item">
|
||||
<i class="pi pi-check-circle text-green-500" />
|
||||
<div>
|
||||
<div class="notif-title">Rôle assigné</div>
|
||||
<div class="notif-time">Il y a 15 min</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="divider" />
|
||||
<li class="notif-footer">
|
||||
<a href="#" class="text-primary">Voir tout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- User Profile -->
|
||||
<li class="topbar-item user-profile elite-user">
|
||||
<a href="#" class="profile-trigger">
|
||||
<div class="avatar-container">
|
||||
<div class="avatar bg-gradient-primary">
|
||||
#{userSessionBean.initials}
|
||||
</div>
|
||||
<span class="status-dot online" />
|
||||
<li class="topbar-item user-profile">
|
||||
<a href="#" class="user-profile-link">
|
||||
<div class="bg-primary text-white border-circle flex align-items-center justify-content-center user-avatar"
|
||||
style="width: 36px; height: 36px; font-size: 14px; font-weight: bold; margin-right: 0.75rem;">
|
||||
#{userSessionBean.initials}
|
||||
</div>
|
||||
|
||||
<div class="user-info">
|
||||
<div class="user-header">
|
||||
<span class="user-name">#{userSessionBean.fullName}</span>
|
||||
<span class="role-badge">#{userSessionBean.primaryRole}</span>
|
||||
</div>
|
||||
<div class="session-timer">
|
||||
<h:panelGroup id="sessionTimerDisplay">
|
||||
<i class="#{sessionMonitor.timeIndicatorIcon} icon-sm" />
|
||||
<span class="#{sessionMonitor.timeIndicatorClass} timer-text">
|
||||
#{sessionMonitor.formattedRemainingTime}
|
||||
</span>
|
||||
</h:panelGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<i class="pi pi-angle-down arrow" />
|
||||
<span class="user-info">
|
||||
<span class="user-name">
|
||||
#{userSessionBean.fullName}
|
||||
<span class="user-status online"></span>
|
||||
<span class="user-separator">|</span>
|
||||
<span class="user-role">#{userSessionBean.primaryRole}</span>
|
||||
</span>
|
||||
<span class="user-email">#{userSessionBean.email}</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- User Dropdown -->
|
||||
<ul class="user-dropdown elite-dropdown">
|
||||
<!-- Header -->
|
||||
<li class="dropdown-header">
|
||||
<div class="header-content">
|
||||
<div class="header-avatar">
|
||||
<div class="avatar-lg bg-gradient-primary">
|
||||
#{userSessionBean.initials}
|
||||
</div>
|
||||
<span class="status-indicator online">
|
||||
<i class="pi pi-circle-fill" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="header-info">
|
||||
<div class="name">#{userSessionBean.fullName}</div>
|
||||
<div class="email">#{userSessionBean.email}</div>
|
||||
<span class="role-tag">#{userSessionBean.primaryRole}</span>
|
||||
<ul class="user-dropdown-menu">
|
||||
<!-- En-tête du menu avec infos utilisateur -->
|
||||
<li class="user-dropdown-header">
|
||||
<div class="user-dropdown-avatar">
|
||||
<div class="bg-primary text-white border-circle flex align-items-center justify-content-center"
|
||||
style="width: 48px; height: 48px; font-size: 20px; font-weight: bold;">
|
||||
#{userSessionBean.initials}
|
||||
</div>
|
||||
<span class="user-status-indicator online"></span>
|
||||
</div>
|
||||
<div class="user-dropdown-info">
|
||||
<div class="user-dropdown-name text-900 font-semibold">#{userSessionBean.fullName}</div>
|
||||
<div class="user-dropdown-email text-600 text-sm">#{userSessionBean.email}</div>
|
||||
<div class="user-dropdown-role text-500 text-xs">#{userSessionBean.primaryRole}</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- Session Card -->
|
||||
<li class="session-card">
|
||||
<div class="card-content">
|
||||
<div class="info-row">
|
||||
<span class="label">
|
||||
<i class="pi pi-server" />
|
||||
Système
|
||||
</span>
|
||||
<span class="value">Lions Platform</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="label">
|
||||
<i class="#{sessionMonitor.timeIndicatorIcon}" />
|
||||
Temps restant
|
||||
</span>
|
||||
<span class="value #{sessionMonitor.timeIndicatorClass}">
|
||||
#{sessionMonitor.formattedRemainingTime}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Séparateur -->
|
||||
<li class="user-dropdown-divider"></li>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill"
|
||||
style="width: #{100 - sessionMonitor.sessionProgressPercent}%" />
|
||||
</div>
|
||||
<div class="progress-label">
|
||||
#{sessionMonitor.remainingMinutes} min
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="divider" />
|
||||
|
||||
<!-- Actions -->
|
||||
<li class="menu-section">
|
||||
<div class="section-title">
|
||||
<i class="pi pi-user" />
|
||||
Mon Compte
|
||||
</div>
|
||||
<!-- Actions principales -->
|
||||
<li class="user-dropdown-section">
|
||||
<div class="section-title">Mon Compte</div>
|
||||
<div class="section-items">
|
||||
<h:link outcome="/pages/user-manager/users/profile" styleClass="menu-item">
|
||||
<i class="pi pi-user-edit" />
|
||||
<h:link outcome="/pages/user-manager/users/profile" styleClass="dropdown-item">
|
||||
<i class="pi pi-user"></i>
|
||||
<span>Mon Profil</span>
|
||||
<i class="pi pi-angle-right arrow-right" />
|
||||
<i class="pi pi-angle-right item-arrow"></i>
|
||||
</h:link>
|
||||
|
||||
<h:link outcome="/pages/user-manager/settings" styleClass="menu-item">
|
||||
<i class="pi pi-cog" />
|
||||
<h:link outcome="/pages/user-manager/settings" styleClass="dropdown-item">
|
||||
<i class="pi pi-cog"></i>
|
||||
<span>Paramètres</span>
|
||||
<i class="pi pi-angle-right arrow-right" />
|
||||
<i class="pi pi-angle-right item-arrow"></i>
|
||||
</h:link>
|
||||
|
||||
<a href="#" class="menu-item">
|
||||
<i class="pi pi-shield" />
|
||||
<a href="#" class="dropdown-item">
|
||||
<i class="pi pi-shield"></i>
|
||||
<span>Sécurité</span>
|
||||
<i class="pi pi-angle-right arrow-right" />
|
||||
<i class="pi pi-angle-right item-arrow"></i>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="divider" />
|
||||
<!-- Séparateur -->
|
||||
<li class="user-dropdown-divider"></li>
|
||||
|
||||
<!-- System -->
|
||||
<li class="menu-section">
|
||||
<div class="section-title">
|
||||
<i class="pi pi-cog" />
|
||||
Système
|
||||
</div>
|
||||
<!-- Actions système -->
|
||||
<li class="user-dropdown-section">
|
||||
<div class="section-items">
|
||||
<a href="#" class="menu-item">
|
||||
<i class="pi pi-users" />
|
||||
<span>Utilisateurs</span>
|
||||
<span class="item-badge">12</span>
|
||||
<i class="pi pi-angle-right arrow-right" />
|
||||
</a>
|
||||
<a href="#" class="menu-item">
|
||||
<i class="pi pi-key" />
|
||||
<span>Rôles & Permissions</span>
|
||||
<i class="pi pi-angle-right arrow-right" />
|
||||
<a href="#" class="dropdown-item">
|
||||
<i class="pi pi-question-circle"></i>
|
||||
<span>Aide & Support</span>
|
||||
</a>
|
||||
<h:form>
|
||||
<p:commandLink action="#{userSessionBean.logout}" styleClass="dropdown-item logout-item">
|
||||
<i class="pi pi-sign-out"></i>
|
||||
<span>Déconnexion</span>
|
||||
</p:commandLink>
|
||||
</h:form>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="divider" />
|
||||
|
||||
<!-- Support -->
|
||||
<li class="menu-section compact">
|
||||
<div class="section-items">
|
||||
<a href="#" class="menu-item">
|
||||
<i class="pi pi-question-circle text-blue-500" />
|
||||
<span>Documentation</span>
|
||||
</a>
|
||||
<a href="#" class="menu-item">
|
||||
<i class="pi pi-info-circle text-cyan-500" />
|
||||
<span>À propos</span>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="divider logout-divider" />
|
||||
|
||||
<!-- Logout -->
|
||||
<li class="logout-section">
|
||||
<h:form>
|
||||
<p:commandLink styleClass="logout-btn"
|
||||
onclick="PF('logoutDialog').show(); return false;">
|
||||
<i class="pi pi-sign-out" />
|
||||
<span>Déconnexion</span>
|
||||
<i class="pi pi-lock ml-auto" />
|
||||
</p:commandLink>
|
||||
</h:form>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<a href="#" class="layout-rightpanel-button" title="Configuration">
|
||||
<i class="pi pi-arrow-left" />
|
||||
<a href="#" class="layout-rightpanel-button">
|
||||
<i class="pi pi-arrow-left"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LOGOUT DIALOG -->
|
||||
<p:dialog id="logoutDialog" widgetVar="logoutDialog" header="Confirmation de déconnexion" modal="true"
|
||||
closable="true" styleClass="elite-dialog" responsive="true" width="450">
|
||||
</ui:composition>
|
||||
|
||||
<div class="dialog-content">
|
||||
<div class="icon-wrapper">
|
||||
<i class="pi pi-sign-out icon-lg" />
|
||||
</div>
|
||||
|
||||
<h3 class="dialog-title">Êtes-vous sûr de vouloir vous déconnecter ?</h3>
|
||||
|
||||
<div class="info-box">
|
||||
<div class="info-item">
|
||||
<i class="pi pi-user" />
|
||||
<span>#{userSessionBean.fullName}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<i class="pi pi-clock" />
|
||||
<span>Session: #{sessionMonitor.formattedRemainingTime}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="warning-text">
|
||||
<i class="pi pi-info-circle" />
|
||||
Vous devrez vous reconnecter pour accéder à l'application.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<f:facet name="footer">
|
||||
<div class="dialog-footer">
|
||||
<p:commandButton value="Annuler" icon="pi pi-times" styleClass="p-button-outlined p-button-secondary"
|
||||
onclick="PF('logoutDialog').hide(); return false;" />
|
||||
|
||||
<h:form>
|
||||
<p:commandButton value="Se déconnecter" icon="pi pi-sign-out" styleClass="p-button-danger"
|
||||
action="#{userSessionBean.logout}" onclick="PF('logoutDialog').hide();" />
|
||||
</h:form>
|
||||
</div>
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
|
||||
<!-- SESSION TIMER AUTO-REFRESH -->
|
||||
<h:form id="sessionTimerForm">
|
||||
<p:poll interval="5" listener="#{sessionMonitor.updateActivity}" update=":sessionTimerDisplay" global="false"
|
||||
autoStart="true" />
|
||||
</h:form>
|
||||
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,170 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant interne: Contenu Attribution de Rôles - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 2.0.0
|
||||
Description: Contenu de l'attribution de rôles, séparé du wrapper form.
|
||||
NE PAS inclure directement - utiliser role-assignment.xhtml.
|
||||
|
||||
Paramètres hérités de role-assignment.xhtml
|
||||
-->
|
||||
|
||||
<p:messages id="roleAssignmentMessages" showDetail="true" closable="true" styleClass="mb-3" />
|
||||
|
||||
<p:panel styleClass="w-full">
|
||||
<f:facet name="header">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-shield text-primary"></i>
|
||||
<span class="font-semibold">Attribution de rôles</span>
|
||||
<c:if test="#{not empty user.username}">
|
||||
<span class="text-color-secondary">- #{user.username}</span>
|
||||
</c:if>
|
||||
</div>
|
||||
</f:facet>
|
||||
|
||||
<!-- Section: Rôles actuels de l'utilisateur -->
|
||||
<h4 class="text-primary mt-0 mb-3">
|
||||
<i class="pi pi-user-edit mr-2"></i>Rôles actuels
|
||||
</h4>
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty userRoles and not userRoles.isEmpty()}">
|
||||
<c:forEach var="userRole" items="#{userRoles}">
|
||||
<div class="flex align-items-center surface-100 border-round-lg px-3 py-2 gap-2">
|
||||
<i class="pi pi-shield text-primary text-sm"></i>
|
||||
<span class="font-medium text-sm">#{userRole.name}</span>
|
||||
<c:if test="#{not empty userRole.typeRole}">
|
||||
<p:tag value="#{userRole.typeRole == 'REALM_ROLE' ? 'Realm' : 'Client'}"
|
||||
severity="#{userRole.typeRole == 'REALM_ROLE' ? 'success' : 'info'}"
|
||||
styleClass="text-xs" />
|
||||
</c:if>
|
||||
<p:commandButton icon="pi pi-times"
|
||||
styleClass="p-button-text p-button-sm p-button-danger p-button-rounded"
|
||||
action="#{roleGestionBean.revokeRoleFromParams}" update="#{update}"
|
||||
title="Révoquer ce rôle" style="width: 1.5rem; height: 1.5rem;">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="roleName" value="#{userRole.name}" />
|
||||
<p:confirm header="Révoquer le rôle"
|
||||
message="Voulez-vous révoquer le rôle '#{userRole.name}' de #{user.username} ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</c:forEach>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<div class="text-color-secondary font-italic p-3 surface-100 border-round w-full text-center">
|
||||
<i class="pi pi-info-circle mr-2"></i>
|
||||
Aucun rôle attribué à cet utilisateur
|
||||
</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
|
||||
<p:separator />
|
||||
|
||||
<!-- Section: Rôles disponibles -->
|
||||
<h4 class="text-primary mt-3 mb-3">
|
||||
<i class="pi pi-list mr-2"></i>Rôles disponibles
|
||||
</h4>
|
||||
|
||||
<!-- Recherche de rôles -->
|
||||
<div class="mb-3">
|
||||
<div class="p-inputgroup" style="max-width: 400px;">
|
||||
<span class="p-inputgroup-addon">
|
||||
<i class="pi pi-search"></i>
|
||||
</span>
|
||||
<p:inputText id="roleSearchInput" value="#{roleGestionBean.roleSearchText}"
|
||||
placeholder="Filtrer les rôles..." styleClass="w-full">
|
||||
<p:ajax event="keyup" delay="300" update="realmRolesPanel clientRolesPanel" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rôles Realm -->
|
||||
<c:if test="#{showRealmRoles}">
|
||||
<p:panel id="realmRolesPanel" header="Rôles Realm" styleClass="mb-3" toggleable="true">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<c:forEach var="role" items="#{availableRoles}">
|
||||
<c:if test="#{role.typeRole == 'REALM_ROLE'}">
|
||||
<!-- Vérifier si déjà assigné -->
|
||||
<c:set var="isAssigned" value="false" />
|
||||
<c:forEach var="ur" items="#{userRoles}">
|
||||
<c:if test="#{ur.id == role.id or ur.name == role.name}">
|
||||
<c:set var="isAssigned" value="true" />
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
|
||||
<div class="flex align-items-center border-1 border-round-lg px-3 py-2 gap-2
|
||||
#{isAssigned ? 'surface-200 border-green-300' : 'surface-50 border-300 hover:surface-100'}
|
||||
transition-all transition-duration-200">
|
||||
<c:choose>
|
||||
<c:when test="#{isAssigned}">
|
||||
<i class="pi pi-check-circle text-green-500"></i>
|
||||
<span class="font-medium text-sm text-green-700">#{role.name}</span>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<i class="pi pi-shield text-400"></i>
|
||||
<span class="font-medium text-sm text-700">#{role.name}</span>
|
||||
<p:commandButton icon="pi pi-plus"
|
||||
styleClass="p-button-text p-button-sm p-button-success p-button-rounded"
|
||||
action="#{roleGestionBean.assignRoleFromParams}" update="#{update}"
|
||||
title="Attribuer ce rôle" style="width: 1.5rem; height: 1.5rem;">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="roleName" value="#{role.name}" />
|
||||
</p:commandButton>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
</div>
|
||||
</p:panel>
|
||||
</c:if>
|
||||
|
||||
<!-- Rôles Client -->
|
||||
<c:if test="#{showClientRoles}">
|
||||
<p:panel id="clientRolesPanel" header="Rôles Client" styleClass="mb-3" toggleable="true">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<c:forEach var="role" items="#{availableRoles}">
|
||||
<c:if test="#{role.typeRole == 'CLIENT_ROLE'}">
|
||||
<c:set var="isAssigned" value="false" />
|
||||
<c:forEach var="ur" items="#{userRoles}">
|
||||
<c:if test="#{ur.id == role.id or ur.name == role.name}">
|
||||
<c:set var="isAssigned" value="true" />
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
|
||||
<div class="flex align-items-center border-1 border-round-lg px-3 py-2 gap-2
|
||||
#{isAssigned ? 'surface-200 border-green-300' : 'surface-50 border-300 hover:surface-100'}
|
||||
transition-all transition-duration-200">
|
||||
<c:choose>
|
||||
<c:when test="#{isAssigned}">
|
||||
<i class="pi pi-check-circle text-green-500"></i>
|
||||
<span class="font-medium text-sm text-green-700">#{role.name}</span>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<i class="pi pi-shield text-400"></i>
|
||||
<span class="font-medium text-sm text-700">#{role.name}</span>
|
||||
<p:commandButton icon="pi pi-plus"
|
||||
styleClass="p-button-text p-button-sm p-button-success p-button-rounded"
|
||||
action="#{roleGestionBean.assignRoleFromParams}" update="#{update}"
|
||||
title="Attribuer ce rôle" style="width: 1.5rem; height: 1.5rem;">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="roleName" value="#{role.name}" />
|
||||
</p:commandButton>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
</div>
|
||||
</p:panel>
|
||||
</c:if>
|
||||
|
||||
</p:panel>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,183 +1,76 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Attribution de Rôles (WOU/DRY Pattern)
|
||||
Composant réutilisable: Attribution de Rôles - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Interface pour attribuer/révoquer des rôles à un utilisateur
|
||||
Version: 2.0.0
|
||||
Description: Interface complète pour attribuer/révoquer des rôles à un utilisateur.
|
||||
Affiche les rôles actuels avec possibilité de révocation, les rôles
|
||||
disponibles classés par type (Realm/Client), et une recherche intégrée.
|
||||
|
||||
Paramètres:
|
||||
- user: UserDTO (requis) - L'utilisateur concerné
|
||||
- availableRoles: List<RoleDTO> (requis) - Liste des rôles disponibles
|
||||
- userRoles: List<RoleDTO> (requis) - Liste des rôles de l'utilisateur
|
||||
- roleBean: String (optionnel) - Nom du bean pour les actions (défaut: "roleGestionBean")
|
||||
- availableRoles: List<RoleDTO> (requis) - Liste des rôles disponibles
|
||||
- userRoles: List<RoleDTO> (requis) - Liste des rôles actuels de l'utilisateur
|
||||
- update: String (défaut: "@form") - Composants à mettre à jour
|
||||
- showRealmRoles: Boolean (défaut: true) - Afficher les rôles Realm
|
||||
- showClientRoles: Boolean (défaut: true) - Afficher les rôles Client
|
||||
- formId: String (défaut: "roleAssignmentForm") - ID du formulaire
|
||||
- useParentForm: Boolean (défaut: false) - Utiliser le formulaire parent
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
1. Attribution simple:
|
||||
1. Attribution dans un formulaire standalone:
|
||||
<ui:include src="/templates/components/role-management/role-assignment.xhtml">
|
||||
<ui:param name="user" value="#{userBean.selectedUser}" />
|
||||
<ui:param name="availableRoles" value="#{roleBean.availableRoles}" />
|
||||
<ui:param name="userRoles" value="#{userBean.userRoles}" />
|
||||
<ui:param name="assignAction" value="#{roleBean.assignRole}" />
|
||||
<ui:param name="revokeAction" value="#{roleBean.revokeRole}" />
|
||||
</ui:include>
|
||||
|
||||
2. Attribution dans un formulaire existant:
|
||||
<ui:include src="/templates/components/role-management/role-assignment.xhtml">
|
||||
<ui:param name="user" value="#{userBean.selectedUser}" />
|
||||
<ui:param name="availableRoles" value="#{roleBean.availableRoles}" />
|
||||
<ui:param name="userRoles" value="#{userBean.userRoles}" />
|
||||
<ui:param name="useParentForm" value="true" />
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
|
||||
<c:set var="formId" value="#{empty formId ? 'roleAssignmentForm' : formId}" />
|
||||
<c:set var="update" value="#{empty update ? '@form' : update}" />
|
||||
<c:set var="showRealmRoles" value="#{empty showRealmRoles ? true : showRealmRoles}" />
|
||||
<c:set var="showClientRoles" value="#{empty showClientRoles ? true : showClientRoles}" />
|
||||
<c:set var="roleBeanName" value="#{empty roleBean ? 'roleGestionBean' : roleBean}" />
|
||||
|
||||
<h:form id="#{formId}">
|
||||
<p:panel header="Attribution de rôles - #{user.username}" styleClass="w-full">
|
||||
|
||||
<!-- Rôles actuels de l'utilisateur -->
|
||||
<h3>Rôles actuels</h3>
|
||||
<div class="flex flex-wrap gap-2 mb-4">
|
||||
<c:forEach var="userRole" items="#{userRoles}">
|
||||
<p:tag
|
||||
value="#{userRole.name}"
|
||||
severity="info"
|
||||
icon="pi pi-shield">
|
||||
<p:commandButton
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-text p-button-sm p-button-danger ml-2"
|
||||
action="#{roleGestionBean.revokeRoleFromParams}"
|
||||
update="#{update}"
|
||||
title="Révoquer le rôle">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="roleName" value="#{userRole.name}" />
|
||||
</p:commandButton>
|
||||
</p:tag>
|
||||
</c:forEach>
|
||||
<c:if test="#{empty userRoles}">
|
||||
<span class="text-color-secondary">Aucun rôle attribué</span>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
<p:separator />
|
||||
|
||||
<!-- Rôles disponibles -->
|
||||
<h3>Rôles disponibles</h3>
|
||||
|
||||
<!-- Rôles Realm -->
|
||||
<c:if test="#{showRealmRoles}">
|
||||
<p:panel header="Rôles Realm" styleClass="mb-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<c:forEach var="role" items="#{availableRoles}">
|
||||
<c:if test="#{role.typeRole == 'REALM_ROLE'}">
|
||||
<c:set var="isAssigned" value="false" />
|
||||
<c:forEach var="userRole" items="#{userRoles}">
|
||||
<c:if test="#{userRole.id == role.id}">
|
||||
<c:set var="isAssigned" value="true" />
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
|
||||
<c:choose>
|
||||
<c:when test="#{isAssigned}">
|
||||
<p:tag
|
||||
value="#{role.name}"
|
||||
severity="success"
|
||||
icon="pi pi-check" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:tag
|
||||
value="#{role.name}"
|
||||
severity="info"
|
||||
icon="pi pi-shield">
|
||||
<p:commandButton
|
||||
icon="pi pi-plus"
|
||||
styleClass="p-button-text p-button-sm p-button-success ml-2"
|
||||
action="#{roleGestionBean.assignRoleFromParams}"
|
||||
update="#{update}"
|
||||
title="Attribuer le rôle">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="roleName" value="#{role.name}" />
|
||||
</p:commandButton>
|
||||
</p:tag>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
</div>
|
||||
</p:panel>
|
||||
</c:if>
|
||||
|
||||
<!-- Rôles Client -->
|
||||
<c:if test="#{showClientRoles}">
|
||||
<p:panel header="Rôles Client" styleClass="mb-3">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<c:forEach var="role" items="#{availableRoles}">
|
||||
<c:if test="#{role.typeRole == 'CLIENT_ROLE'}">
|
||||
<c:set var="isAssigned" value="false" />
|
||||
<c:forEach var="userRole" items="#{userRoles}">
|
||||
<c:if test="#{userRole.id == role.id}">
|
||||
<c:set var="isAssigned" value="true" />
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
|
||||
<c:choose>
|
||||
<c:when test="#{isAssigned}">
|
||||
<p:tag
|
||||
value="#{role.name}"
|
||||
severity="success"
|
||||
icon="pi pi-check" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:tag
|
||||
value="#{role.name}"
|
||||
severity="info"
|
||||
icon="pi pi-shield">
|
||||
<p:commandButton
|
||||
icon="pi pi-plus"
|
||||
styleClass="p-button-text p-button-sm p-button-success ml-2"
|
||||
action="#{roleGestionBean.assignRoleFromParams}"
|
||||
update="#{update}"
|
||||
title="Attribuer le rôle">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="roleName" value="#{role.name}" />
|
||||
</p:commandButton>
|
||||
</p:tag>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
</div>
|
||||
</p:panel>
|
||||
</c:if>
|
||||
|
||||
<!-- Recherche de rôles -->
|
||||
<p:separator />
|
||||
<h3>Rechercher un rôle</h3>
|
||||
<div class="flex gap-2 mb-3">
|
||||
<p:inputText
|
||||
value="#{roleGestionBean.roleSearchText}"
|
||||
placeholder="Rechercher un rôle..."
|
||||
styleClass="flex-1">
|
||||
<p:ajax event="keyup"
|
||||
delay="300"
|
||||
update="@parent" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
|
||||
<div id="roleSearchResults" class="flex flex-wrap gap-2">
|
||||
<!-- Résultats de recherche -->
|
||||
</div>
|
||||
|
||||
</p:panel>
|
||||
</h:form>
|
||||
|
||||
</ui:composition>
|
||||
<c:set var="useParentForm" value="#{empty useParentForm ? false : useParentForm}" />
|
||||
|
||||
<!-- Contenu du composant (utilisable avec ou sans form wrapper) -->
|
||||
<c:choose>
|
||||
<c:when test="#{useParentForm}">
|
||||
<ui:fragment>
|
||||
<ui:include src="/templates/components/role-management/role-assignment-content.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="availableRoles" value="#{availableRoles}" />
|
||||
<ui:param name="userRoles" value="#{userRoles}" />
|
||||
<ui:param name="update" value="#{update}" />
|
||||
<ui:param name="showRealmRoles" value="#{showRealmRoles}" />
|
||||
<ui:param name="showClientRoles" value="#{showClientRoles}" />
|
||||
</ui:include>
|
||||
</ui:fragment>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<h:form id="#{formId}">
|
||||
<ui:include src="/templates/components/role-management/role-assignment-content.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="availableRoles" value="#{availableRoles}" />
|
||||
<ui:param name="userRoles" value="#{userRoles}" />
|
||||
<ui:param name="update" value="#{update}" />
|
||||
<ui:param name="showRealmRoles" value="#{showRealmRoles}" />
|
||||
<ui:param name="showClientRoles" value="#{showClientRoles}" />
|
||||
</ui:include>
|
||||
</h:form>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,26 +1,26 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Carte Rôle (WOU/DRY Pattern)
|
||||
Composant réutilisable: Carte Rôle - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Affiche une carte rôle avec informations principales et actions
|
||||
Version: 2.0.0
|
||||
Description: Affiche une carte rôle avec icône, type, description et actions.
|
||||
Prend en charge les différents types de rôles (Realm, Client, Composite).
|
||||
|
||||
Paramètres:
|
||||
- role: RoleDTO (requis) - Le rôle à afficher
|
||||
- showActions: Boolean (défaut: true) - Afficher les boutons d'action
|
||||
- showDescription: Boolean (défaut: true) - Afficher la description
|
||||
- showCompositeInfo: Boolean (défaut: true) - Afficher les infos de rôle composite
|
||||
- clickable: Boolean (défaut: true) - Rendre la carte cliquable
|
||||
- clickable: Boolean (défaut: true) - Rendre le titre cliquable
|
||||
- outcome: String (optionnel) - Page de destination au clic
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
- editOutcome: String (optionnel) - Page d'édition
|
||||
- deleteAction: MethodExpression (optionnel) - Action de suppression
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
@@ -29,127 +29,143 @@
|
||||
<ui:param name="role" value="#{roleBean.selectedRole}" />
|
||||
</ui:include>
|
||||
|
||||
2. Carte avec actions:
|
||||
<ui:include src="/templates/components/role-management/role-card.xhtml">
|
||||
<ui:param name="role" value="#{roleBean.selectedRole}" />
|
||||
<ui:param name="showActions" value="true" />
|
||||
<ui:param name="outcome" value="/pages/user-manager/roles/details" />
|
||||
</ui:include>
|
||||
2. Carte pour liste:
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<ui:include src="/templates/components/role-management/role-card.xhtml">
|
||||
<ui:param name="role" value="#{r}" />
|
||||
<ui:param name="deleteAction" value="#{roleBean.deleteRole(r.id)}" />
|
||||
</ui:include>
|
||||
</div>
|
||||
-->
|
||||
|
||||
|
||||
<c:set var="showActions" value="#{empty showActions ? true : showActions}" />
|
||||
<c:set var="showDescription" value="#{empty showDescription ? true : showDescription}" />
|
||||
<c:set var="showCompositeInfo" value="#{empty showCompositeInfo ? true : showCompositeInfo}" />
|
||||
<c:set var="clickable" value="#{empty clickable ? true : clickable}" />
|
||||
|
||||
<p:card styleClass="role-card #{styleClass}" rendered="#{not empty role}">
|
||||
<c:set var="editOutcome" value="#{empty editOutcome ? '/pages/user-manager/roles/edit' : editOutcome}" />
|
||||
|
||||
<!-- Détermination des styles selon le type de rôle -->
|
||||
<c:set var="icon" value="pi-shield" />
|
||||
<c:set var="severity" value="info" />
|
||||
<c:set var="typeLabel" value="Rôle" />
|
||||
|
||||
<c:choose>
|
||||
<c:when test="#{role.typeRole == 'REALM_ROLE'}">
|
||||
<c:set var="severity" value="success" />
|
||||
<c:set var="typeLabel" value="Realm" />
|
||||
<c:set var="iconColor" value="text-green-500" />
|
||||
<c:set var="bgColor" value="bg-green-100" />
|
||||
</c:when>
|
||||
<c:when test="#{role.typeRole == 'CLIENT_ROLE'}">
|
||||
<c:set var="severity" value="info" />
|
||||
<c:set var="typeLabel" value="Client" />
|
||||
<c:set var="iconColor" value="text-blue-500" />
|
||||
<c:set var="bgColor" value="bg-blue-100" />
|
||||
</c:when>
|
||||
<c:when test="#{role.typeRole == 'COMPOSITE_ROLE'}">
|
||||
<c:set var="severity" value="warning" />
|
||||
<c:set var="typeLabel" value="Composite" />
|
||||
<c:set var="iconColor" value="text-orange-500" />
|
||||
<c:set var="bgColor" value="bg-orange-100" />
|
||||
<c:set var="icon" value="pi-sitemap" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<c:set var="severity" value="secondary" />
|
||||
<c:set var="iconColor" value="text-gray-500" />
|
||||
<c:set var="bgColor" value="surface-100" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
<p:card styleClass="role-card shadow-1 hover:shadow-3 transition-all transition-duration-200 #{styleClass}"
|
||||
rendered="#{not empty role}">
|
||||
|
||||
<f:facet name="header">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-shield text-2xl text-primary"></i>
|
||||
<div class="flex align-items-center justify-content-between p-3 border-bottom-1 surface-border">
|
||||
<div class="flex align-items-center gap-3">
|
||||
<div class="flex align-items-center justify-content-center border-circle #{bgColor}"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi #{icon} #{iconColor} text-lg"></i>
|
||||
</div>
|
||||
<div class="flex flex-column">
|
||||
<h3 class="m-0">#{role.name}</h3>
|
||||
<span class="text-color-secondary text-sm">
|
||||
<c:choose>
|
||||
<c:when test="#{role.typeRole == 'REALM_ROLE'}">Rôle Realm</c:when>
|
||||
<c:when test="#{role.typeRole == 'CLIENT_ROLE'}">Rôle Client</c:when>
|
||||
<c:when test="#{role.typeRole == 'COMPOSITE_ROLE'}">Rôle Composite</c:when>
|
||||
<c:otherwise>Rôle</c:otherwise>
|
||||
</c:choose>
|
||||
</span>
|
||||
<c:choose>
|
||||
<c:when test="#{clickable}">
|
||||
<h:link outcome="#{not empty outcome ? outcome : '/pages/user-manager/roles/details'}"
|
||||
styleClass="text-900 font-semibold text-lg no-underline hover:text-primary transition-colors transition-duration-200">
|
||||
<f:param name="roleId" value="#{role.id}" />
|
||||
#{role.name}
|
||||
</h:link>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<span class="text-900 font-semibold text-lg">#{role.name}</span>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<span class="text-color-secondary text-xs">#{typeLabel}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p:tag
|
||||
value="#{role.typeRole != null ? role.typeRole : 'ROLE'}"
|
||||
severity="#{role.typeRole == 'REALM_ROLE' ? 'success' : role.typeRole == 'CLIENT_ROLE' ? 'info' : 'warning'}" />
|
||||
|
||||
<c:if test="#{role.composite}">
|
||||
<i class="pi pi-sitemap text-orange-500" title="Rôle Composite"></i>
|
||||
</c:if>
|
||||
</div>
|
||||
</f:facet>
|
||||
|
||||
<div class="role-card-content">
|
||||
|
||||
<div class="role-card-content pt-3">
|
||||
<!-- Description -->
|
||||
<c:if test="#{showDescription and not empty role.description}">
|
||||
<p class="text-color-secondary mb-3">#{role.description}</p>
|
||||
<c:if test="#{showDescription}">
|
||||
<div class="mb-3" style="min-height: 3rem;">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty role.description}">
|
||||
<p class="text-color-secondary text-sm m-0 line-height-3 text-overflow-ellipsis overflow-hidden"
|
||||
style="display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;">
|
||||
#{role.description}
|
||||
</p>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<span class="text-color-secondary text-sm font-italic opacity-60">Aucune description</span>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<!-- Informations -->
|
||||
<div class="flex flex-column gap-2 mb-3">
|
||||
|
||||
<!-- Informations contextuelles -->
|
||||
<div class="flex flex-column gap-2">
|
||||
<c:if test="#{not empty role.realmName}">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-globe text-color-secondary"></i>
|
||||
<span>Realm: <strong>#{role.realmName}</strong></span>
|
||||
<i class="pi pi-globe text-color-secondary text-xs"></i>
|
||||
<span class="text-sm">#{role.realmName}</span>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
|
||||
<c:if test="#{not empty role.clientId}">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-desktop text-color-secondary"></i>
|
||||
<span>Client: <strong>#{role.clientId}</strong></span>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<!-- Rôle composite -->
|
||||
<c:if test="#{showCompositeInfo and role.composite}">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-sitemap text-color-secondary"></i>
|
||||
<span class="text-warning">Rôle composite</span>
|
||||
<i class="pi pi-desktop text-color-secondary text-xs"></i>
|
||||
<span class="text-sm">Client: #{role.clientId}</span>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Actions -->
|
||||
<f:facet name="footer">
|
||||
<c:if test="#{showActions}">
|
||||
<div class="flex gap-2 justify-content-end">
|
||||
<p:commandButton
|
||||
icon="pi pi-eye"
|
||||
title="Voir les détails"
|
||||
styleClass="p-button-text p-button-sm"
|
||||
outcome="#{not empty outcome ? outcome : '/pages/user-manager/roles/details'}"
|
||||
rendered="#{clickable}">
|
||||
<div class="flex gap-2 justify-content-end pt-2 border-top-1 surface-border">
|
||||
<p:commandButton icon="pi pi-pencil" title="Modifier"
|
||||
styleClass="p-button-text p-button-sm p-button-warning p-button-rounded"
|
||||
outcome="#{editOutcome}">
|
||||
<f:param name="roleId" value="#{role.id}" />
|
||||
</p:commandButton>
|
||||
|
||||
<p:commandButton
|
||||
icon="pi pi-pencil"
|
||||
title="Modifier"
|
||||
styleClass="p-button-text p-button-sm p-button-warning"
|
||||
outcome="/pages/user-manager/roles/edit">
|
||||
<f:param name="roleId" value="#{role.id}" />
|
||||
</p:commandButton>
|
||||
|
||||
<p:commandButton
|
||||
icon="pi pi-trash"
|
||||
title="Supprimer"
|
||||
styleClass="p-button-text p-button-sm p-button-danger"
|
||||
onclick="PF('confirmDeleteRoleDialog_#{fn:replace(fn:replace(role.id != null ? role.id : role.name, '-', '_'), ':', '_')}').show()" />
|
||||
|
||||
<c:if test="#{not empty deleteAction}">
|
||||
<p:commandButton icon="pi pi-trash" title="Supprimer"
|
||||
styleClass="p-button-text p-button-sm p-button-danger p-button-rounded"
|
||||
action="#{deleteAction}" update="@form">
|
||||
<p:confirm header="Confirmation" message="Supprimer le rôle #{role.name} ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:if>
|
||||
</f:facet>
|
||||
</p:card>
|
||||
|
||||
<!-- Dialog de confirmation de suppression avec ID unique basé sur l'ID ou le nom du rôle -->
|
||||
<c:if test="#{not empty role.name}">
|
||||
<c:set var="dialogId" value="confirmDeleteRoleDialog_#{fn:replace(fn:replace(role.id != null ? role.id : role.name, '-', '_'), ':', '_')}" />
|
||||
<p:confirmDialog
|
||||
id="#{dialogId}"
|
||||
widgetVar="#{dialogId}"
|
||||
message="Êtes-vous sûr de vouloir supprimer le rôle #{role.name} ?"
|
||||
header="Confirmation de suppression"
|
||||
severity="warn">
|
||||
<p:commandButton
|
||||
value="Oui"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-danger"
|
||||
action="#{roleGestionBean.deleteRealmRole(role.name)}"
|
||||
update="@form"
|
||||
oncomplete="PF('#{dialogId}').hide()" />
|
||||
<p:commandButton
|
||||
value="Non"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
onclick="PF('#{dialogId}').hide()" />
|
||||
</p:confirmDialog>
|
||||
</c:if>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,131 +1,165 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<p:panel header="#{mode == 'create' ? 'Nouveau Rôle' : 'Modifier Rôle'}"
|
||||
styleClass="w-full">
|
||||
<!--
|
||||
Composant réutilisable: Contenu du formulaire Rôle - Écosystème LionsDev
|
||||
|
||||
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 2.0.0
|
||||
Description: Corps du formulaire rôle (création/édition) avec validation
|
||||
et messages d'erreur. Doit être contenu dans un h:form ou utilisé
|
||||
via role-form.xhtml qui gère automatiquement le wrapper.
|
||||
|
||||
Paramètres hérités de role-form.xhtml:
|
||||
- role: RoleDTO (requis)
|
||||
- mode: String ("create" | "edit")
|
||||
- showRealmSelector: Boolean
|
||||
- showClientSelector: Boolean
|
||||
- showCompositeOptions: Boolean
|
||||
- readonly: Boolean
|
||||
- hasSubmitAction: Boolean
|
||||
- submitAction: MethodExpression
|
||||
- submitOutcome: String
|
||||
- update: String
|
||||
- cancelOutcome: String
|
||||
-->
|
||||
|
||||
<p:messages id="roleFormMessages" showDetail="true" closable="true" styleClass="mb-3" />
|
||||
|
||||
<p:panel header="#{mode == 'create' ? 'Nouveau Rôle' : 'Modifier Rôle'}" styleClass="w-full">
|
||||
|
||||
<!-- Section: Informations du rôle -->
|
||||
<h4 class="text-primary mt-0 mb-3">
|
||||
<i class="pi pi-shield mr-2"></i>Informations du rôle
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Nom du rôle -->
|
||||
<p:outputLabel for="roleName" value="Nom du rôle *" />
|
||||
<p:inputText id="roleName"
|
||||
value="#{role.name}"
|
||||
required="true"
|
||||
readonly="#{readonly}"
|
||||
placeholder="ADMIN, USER, MODERATOR..."
|
||||
styleClass="w-full">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
<f:validateRegex pattern="^[A-Z_]+$" />
|
||||
</p:inputText>
|
||||
|
||||
<!-- Description -->
|
||||
<p:outputLabel for="description" value="Description" />
|
||||
<p:inputTextarea id="description"
|
||||
value="#{role.description}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Description du rôle..."
|
||||
rows="3"
|
||||
styleClass="w-full" />
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="roleName" value="Nom du rôle" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:inputText id="roleName" value="#{role.name}" required="true" readonly="#{readonly}"
|
||||
placeholder="ADMIN, USER, MODERATOR..." styleClass="w-full">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
<f:validateRegex pattern="^[A-Z][A-Z0-9_]*$" />
|
||||
</p:inputText>
|
||||
<p:message for="roleName" display="text" styleClass="mt-1" />
|
||||
<small class="text-color-secondary block mt-1">
|
||||
Lettres majuscules, chiffres et underscores uniquement (ex: ADMIN_USER)
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Type de rôle -->
|
||||
<p:outputLabel for="typeRole" value="Type de rôle *" />
|
||||
<p:selectOneMenu id="typeRole"
|
||||
value="#{role.typeRole}"
|
||||
required="true"
|
||||
readonly="#{readonly or mode == 'edit'}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<f:selectItem itemLabel="Rôle Realm" itemValue="REALM_ROLE" />
|
||||
<f:selectItem itemLabel="Rôle Client" itemValue="CLIENT_ROLE" />
|
||||
<f:selectItem itemLabel="Rôle Composite" itemValue="COMPOSITE_ROLE" />
|
||||
</p:selectOneMenu>
|
||||
|
||||
<!-- Realm (si affiché) -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="typeRole" value="Type de rôle" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:selectOneMenu id="typeRole" value="#{role.typeRole}" required="true"
|
||||
readonly="#{readonly or mode == 'edit'}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItem itemLabel="Rôle Realm" itemValue="REALM_ROLE" />
|
||||
<f:selectItem itemLabel="Rôle Client" itemValue="CLIENT_ROLE" />
|
||||
<f:selectItem itemLabel="Rôle Composite" itemValue="COMPOSITE_ROLE" />
|
||||
</p:selectOneMenu>
|
||||
<p:message for="typeRole" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="col-12">
|
||||
<div class="field">
|
||||
<p:outputLabel for="description" value="Description" styleClass="font-medium" />
|
||||
<p:inputTextarea id="description" value="#{role.description}" readonly="#{readonly}"
|
||||
placeholder="Description du rôle..." rows="3" autoResize="true" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p:separator />
|
||||
|
||||
<!-- Section: Configuration -->
|
||||
<h4 class="text-primary mt-2 mb-3">
|
||||
<i class="pi pi-cog mr-2"></i>Configuration
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Realm -->
|
||||
<c:if test="#{showRealmSelector}">
|
||||
<p:outputLabel for="realmName" value="Realm *" />
|
||||
<p:selectOneMenu id="realmName"
|
||||
value="#{role.realmName}"
|
||||
required="#{showRealmSelector}"
|
||||
readonly="#{readonly}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<f:selectItems value="#{roleBean.availableRealms}" />
|
||||
</p:selectOneMenu>
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="realmName" value="Realm" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:selectOneMenu id="realmName" value="#{role.realmName}" required="#{showRealmSelector}"
|
||||
readonly="#{readonly}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{roleBean.availableRealms}" />
|
||||
</p:selectOneMenu>
|
||||
<p:message for="realmName" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<!-- Client (si affiché) -->
|
||||
|
||||
<!-- Client -->
|
||||
<c:if test="#{showClientSelector}">
|
||||
<p:outputLabel for="clientId" value="Client *" />
|
||||
<p:selectOneMenu id="clientId"
|
||||
value="#{role.clientId}"
|
||||
required="#{showClientSelector}"
|
||||
readonly="#{readonly}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<f:selectItems value="#{roleBean.availableClients}" />
|
||||
</p:selectOneMenu>
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="clientId" value="Client" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:selectOneMenu id="clientId" value="#{role.clientId}" required="#{showClientSelector}"
|
||||
readonly="#{readonly}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{roleBean.availableClients}" />
|
||||
</p:selectOneMenu>
|
||||
<p:message for="clientId" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
|
||||
<!-- Rôle composite -->
|
||||
<c:if test="#{showCompositeOptions}">
|
||||
<p:outputLabel for="composite" value="Rôle composite" />
|
||||
<p:selectBooleanCheckbox id="composite"
|
||||
value="#{role.composite}"
|
||||
readonly="#{readonly}" />
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<div class="flex align-items-center gap-2 mt-3">
|
||||
<p:selectBooleanCheckbox id="composite" value="#{role.composite}" readonly="#{readonly}" />
|
||||
<p:outputLabel for="composite" value="Rôle composite" styleClass="font-medium" />
|
||||
</div>
|
||||
<small class="text-color-secondary block mt-1">
|
||||
Un rôle composite regroupe plusieurs sous-rôles
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
</p:panelGrid>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Boutons d'action -->
|
||||
<f:facet name="footer">
|
||||
<div class="flex gap-2 justify-content-end">
|
||||
<c:if test="#{not readonly}">
|
||||
<c:choose>
|
||||
<!-- Si hasSubmitAction est explicitement défini à true, utiliser action -->
|
||||
<c:when test="#{hasSubmitAction == true}">
|
||||
<p:commandButton
|
||||
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success"
|
||||
action="#{submitAction}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="@form" />
|
||||
<p:commandButton value="#{mode == 'create' ? 'Créer le rôle' : 'Enregistrer'}"
|
||||
icon="pi pi-check" styleClass="p-button-success" action="#{submitAction}"
|
||||
update="#{not empty update ? update : '@form'}" process="@form" validateClient="true" />
|
||||
</c:when>
|
||||
<!-- Si submitOutcome est fourni, utiliser outcome -->
|
||||
<c:when test="#{not empty submitOutcome}">
|
||||
<p:commandButton
|
||||
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success"
|
||||
outcome="#{submitOutcome}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="@form" />
|
||||
<p:commandButton value="#{mode == 'create' ? 'Créer le rôle' : 'Enregistrer'}"
|
||||
icon="pi pi-check" styleClass="p-button-success" outcome="#{submitOutcome}" />
|
||||
</c:when>
|
||||
<!-- Sinon, essayer d'utiliser submitAction si fourni -->
|
||||
<c:otherwise>
|
||||
<p:commandButton
|
||||
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success"
|
||||
action="#{submitAction}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="@form" />
|
||||
<p:commandButton value="#{mode == 'create' ? 'Créer le rôle' : 'Enregistrer'}"
|
||||
icon="pi pi-check" styleClass="p-button-success" action="#{submitAction}"
|
||||
update="#{not empty update ? update : '@form'}" process="@form" validateClient="true" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if>
|
||||
<p:commandButton
|
||||
value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
onclick="PF('createRealmRoleDialog').hide(); PF('createClientRoleDialog').hide();"
|
||||
<p:commandButton value="Annuler" icon="pi pi-times" styleClass="p-button-secondary p-button-outlined"
|
||||
outcome="#{not empty cancelOutcome ? cancelOutcome : '/pages/user-manager/roles/list'}"
|
||||
immediate="true" />
|
||||
</div>
|
||||
</f:facet>
|
||||
</p:panel>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,107 +1,141 @@
|
||||
<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"
|
||||
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Bouton Action Utilisateur (WOU/DRY Pattern)
|
||||
Composant réutilisable: Bouton Action Utilisateur - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Bouton générique pour actions utilisateur
|
||||
Version: 2.0.0
|
||||
Description: Bouton générique pour actions utilisateur avec support complet de
|
||||
navigation (outcome), d'action serveur (action), et d'action client (onclick).
|
||||
Supporte aussi le mode icon-only, les tooltips et les confirmations.
|
||||
|
||||
Paramètres:
|
||||
- value: String (requis) - Texte du bouton
|
||||
- icon: String (optionnel) - Classe d'icône PrimeIcons
|
||||
- hasAction: Boolean (défaut: false) - Indique si une action est fournie
|
||||
- icon: String (optionnel) - Classe d'icône PrimeIcons complète (ex: "pi pi-user-plus")
|
||||
- hasAction: Boolean (défaut: false) - Indique si une action serveur est fournie
|
||||
- action: MethodExpression (optionnel) - Action à exécuter (requis si hasAction=true)
|
||||
- hasOutcome: Boolean (défaut: false) - Indique si un outcome est fourni
|
||||
- outcome: String (optionnel) - Page de redirection (requis si hasOutcome=true)
|
||||
- onclick: String (optionnel) - Code JavaScript à exécuter au clic
|
||||
- severity: String (défaut: "primary") - Severity: "primary", "success", "warning", "danger", "info", "secondary"
|
||||
- size: String (défaut: "normal") - Taille: "small", "normal", "large"
|
||||
- severity: String (défaut: "primary") - "primary", "success", "warning", "danger", "info", "secondary", "help"
|
||||
- size: String (défaut: "normal") - "small", "normal", "large"
|
||||
- disabled: Boolean (défaut: false) - Désactiver le bouton
|
||||
- update: String (optionnel) - Composants à mettre à jour
|
||||
- process: String (défaut: "@this") - Composants à traiter
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
- title: String (optionnel) - Tooltip du bouton
|
||||
- iconOnly: Boolean (défaut: false) - Mode bouton icône seulement
|
||||
- outlined: Boolean (défaut: false) - Style outlined
|
||||
- rounded: Boolean (défaut: false) - Style arrondi
|
||||
- confirmMessage: String (optionnel) - Message de confirmation avant l'action
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
1. Bouton simple:
|
||||
1. Bouton avec action serveur:
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Créer Utilisateur" />
|
||||
<ui:param name="icon" value="pi-user-plus" />
|
||||
<ui:param name="icon" value="pi pi-user-plus" />
|
||||
<ui:param name="hasAction" value="true" />
|
||||
<ui:param name="action" value="#{userBean.createUser}" />
|
||||
<ui:param name="severity" value="success" />
|
||||
</ui:include>
|
||||
|
||||
2. Bouton avec redirection:
|
||||
2. Bouton de navigation:
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Voir Profil" />
|
||||
<ui:param name="icon" value="pi-eye" />
|
||||
<ui:param name="icon" value="pi pi-eye" />
|
||||
<ui:param name="hasOutcome" value="true" />
|
||||
<ui:param name="outcome" value="/pages/user-manager/users/profile" />
|
||||
</ui:include>
|
||||
|
||||
3. Bouton icône seulement:
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Supprimer" />
|
||||
<ui:param name="icon" value="pi pi-trash" />
|
||||
<ui:param name="iconOnly" value="true" />
|
||||
<ui:param name="severity" value="danger" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="hasAction" value="true" />
|
||||
<ui:param name="action" value="#{userBean.delete}" />
|
||||
</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}" />
|
||||
<c:set var="process" value="#{empty process ? '@this' : process}" />
|
||||
<c:set var="hasAction" value="#{empty hasAction ? false : hasAction}" />
|
||||
<c:set var="hasOutcome" value="#{empty hasOutcome ? false : hasOutcome}" />
|
||||
|
||||
<c:set var="iconOnly" value="#{empty iconOnly ? false : iconOnly}" />
|
||||
<c:set var="outlined" value="#{empty outlined ? false : outlined}" />
|
||||
<c:set var="rounded" value="#{empty rounded ? false : rounded}" />
|
||||
|
||||
<!-- Construire la classe CSS -->
|
||||
<c:set var="buttonClass" value="p-button-#{severity}" />
|
||||
|
||||
<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>
|
||||
<c:if test="#{outlined}">
|
||||
<c:set var="buttonClass" value="#{buttonClass} p-button-outlined" />
|
||||
</c:if>
|
||||
<c:if test="#{rounded}">
|
||||
<c:set var="buttonClass" value="#{buttonClass} p-button-rounded" />
|
||||
</c:if>
|
||||
<c:if test="#{iconOnly}">
|
||||
<c:set var="buttonClass" value="#{buttonClass} p-button-text" />
|
||||
</c:if>
|
||||
<c:if test="#{not empty styleClass}">
|
||||
<c:set var="buttonClass" value="#{buttonClass} #{styleClass}" />
|
||||
</c:if>
|
||||
|
||||
<c:choose>
|
||||
<!-- Bouton avec action serveur -->
|
||||
<c:when test="#{hasAction}">
|
||||
<fr:commandButton
|
||||
value="#{value}"
|
||||
icon="#{not empty icon ? icon : ''}"
|
||||
severity="#{severity}"
|
||||
size="#{size != 'normal' ? size : null}"
|
||||
styleClass="#{styleClass}"
|
||||
disabled="#{disabled}"
|
||||
action="#{action}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="#{process}"
|
||||
onclick="#{not empty onclick ? onclick : ''}" />
|
||||
<p:commandButton value="#{iconOnly ? '' : value}" icon="#{not empty icon ? icon : ''}"
|
||||
styleClass="#{buttonClass}" disabled="#{disabled}" action="#{action}"
|
||||
update="#{not empty update ? update : '@form'}" process="#{process}"
|
||||
onclick="#{not empty onclick ? onclick : ''}"
|
||||
title="#{not empty title ? title : (iconOnly ? value : '')}">
|
||||
<c:if test="#{not empty confirmMessage}">
|
||||
<p:confirm header="Confirmation" message="#{confirmMessage}" icon="pi pi-exclamation-triangle" />
|
||||
</c:if>
|
||||
</p:commandButton>
|
||||
</c:when>
|
||||
|
||||
<!-- Bouton avec navigation -->
|
||||
<!-- Bouton avec navigation -->
|
||||
<c:when test="#{hasOutcome}">
|
||||
<fr:commandButton
|
||||
value="#{value}"
|
||||
icon="#{not empty icon ? icon : ''}"
|
||||
severity="#{severity}"
|
||||
size="#{size != 'normal' ? size : null}"
|
||||
styleClass="#{styleClass}"
|
||||
disabled="#{disabled}"
|
||||
outcome="#{outcome}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="#{process}"
|
||||
onclick="#{not empty onclick ? onclick : ''}" />
|
||||
<p:button value="#{iconOnly ? '' : value}" icon="#{not empty icon ? icon : ''}" styleClass="#{buttonClass}"
|
||||
disabled="#{disabled}" outcome="#{outcome}"
|
||||
title="#{not empty title ? title : (iconOnly ? value : '')}">
|
||||
<c:if test="#{not empty paramUserId}">
|
||||
<f:param name="userId" value="#{paramUserId}" />
|
||||
</c:if>
|
||||
<c:if test="#{not empty paramRealm}">
|
||||
<f:param name="realm" value="#{paramRealm}" />
|
||||
</c:if>
|
||||
</p:button>
|
||||
</c:when>
|
||||
|
||||
<!-- Bouton client-side (onclick uniquement) -->
|
||||
<c:when test="#{not empty onclick}">
|
||||
<fr:commandButton
|
||||
value="#{value}"
|
||||
icon="#{not empty icon ? icon : ''}"
|
||||
severity="#{severity}"
|
||||
size="#{size != 'normal' ? size : null}"
|
||||
styleClass="#{styleClass}"
|
||||
disabled="#{disabled}"
|
||||
type="button"
|
||||
onclick="#{onclick}" />
|
||||
<p:commandButton value="#{iconOnly ? '' : value}" icon="#{not empty icon ? icon : ''}"
|
||||
styleClass="#{buttonClass}" disabled="#{disabled}" type="button" onclick="#{onclick}"
|
||||
title="#{not empty title ? title : (iconOnly ? value : '')}" />
|
||||
</c:when>
|
||||
|
||||
<!-- Bouton sans action définie (désactivé avec tooltip) -->
|
||||
<c:otherwise>
|
||||
<fr:commandButton
|
||||
value="#{value}"
|
||||
icon="#{not empty icon ? icon : ''}"
|
||||
severity="#{severity}"
|
||||
size="#{size != 'normal' ? size : null}"
|
||||
styleClass="#{styleClass}"
|
||||
disabled="true"
|
||||
title="Aucune action définie" />
|
||||
<p:commandButton value="#{iconOnly ? '' : value}" icon="#{not empty icon ? icon : ''}"
|
||||
styleClass="#{buttonClass}" disabled="true"
|
||||
title="#{not empty title ? title : 'Aucune action définie'}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,41 +1,55 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Contenu Carte KPI - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 2.1.0
|
||||
Description: Contenu interne d'une carte KPI. Ce composant est inclus par
|
||||
kpi-card.xhtml et ne devrait pas être utilisé directement.
|
||||
Affiche le titre, la valeur, le sous-titre, l'indicateur de
|
||||
croissance/statut, et une barre de progression optionnelle.
|
||||
|
||||
Paramètres hérités de kpi-card.xhtml:
|
||||
- title, value, icon, iconColor, subtitle
|
||||
- growthValue, growthLabel, growthType, showGrowth
|
||||
- progressValue, showProgress
|
||||
- statusIcon, statusLabel, statusValue
|
||||
- noDataLabel
|
||||
-->
|
||||
|
||||
<div class="p-4" style="min-height: 9rem;">
|
||||
<!-- Header: Titre et Icône -->
|
||||
<div class="flex align-items-center justify-content-between mb-3">
|
||||
<span class="block text-600 font-medium text-sm">#{title}</span>
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<div class="flex align-items-center justify-content-center surface-100 border-round-lg"
|
||||
style="width: 2.5rem; height: 2.5rem;">
|
||||
<i class="pi #{icon} text-#{iconColor} text-lg"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Valeur principale -->
|
||||
<div class="text-900 font-bold text-2xl mb-2">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty value}">
|
||||
<c:set var="valueStr" value="#{String.valueOf(value)}" />
|
||||
<c:set var="valueStr" value="#{value.toString()}" />
|
||||
<c:choose>
|
||||
<c:when test="#{fn:startsWith(valueStr, '-') and fn:length(valueStr) == 1}">0</c:when>
|
||||
<c:when test="#{fn:startsWith(valueStr, '...')}">0</c:when>
|
||||
<c:when test="#{valueStr == '-' or valueStr == '...'}">0</c:when>
|
||||
<c:otherwise>#{value}</c:otherwise>
|
||||
</c:choose>
|
||||
</c:when>
|
||||
<c:otherwise>0</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Sous-titre -->
|
||||
<c:if test="#{not empty subtitle}">
|
||||
<div class="text-500 text-xs mb-2">#{subtitle}</div>
|
||||
</c:if>
|
||||
|
||||
|
||||
<!-- Section Croissance ou Statut -->
|
||||
<c:choose>
|
||||
<!-- Mode Statut (statusIcon fourni) -->
|
||||
@@ -49,67 +63,56 @@
|
||||
</div>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<div class="text-500 text-xs mb-2">Aucun #{statusLabel}</div>
|
||||
<div class="text-500 text-xs mb-2">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty statusLabel}">Aucun #{statusLabel}</c:when>
|
||||
<c:otherwise>#{noDataLabel}</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:when>
|
||||
<!-- Mode Croissance -->
|
||||
<c:otherwise>
|
||||
<c:choose>
|
||||
<!-- Croissance en nombre -->
|
||||
<c:when test="#{growthType == 'number'}">
|
||||
<c:choose>
|
||||
<c:when test="#{showGrowth and not empty growthValue and growthValue != '0' and growthValue != '0.0'}">
|
||||
<div class="flex align-items-center mb-2">
|
||||
<c:when
|
||||
test="#{showGrowth and not empty growthValue and growthValue != '0' and growthValue != '0.0'}">
|
||||
<div class="flex align-items-center mb-2">
|
||||
<c:choose>
|
||||
<!-- Croissance positive -->
|
||||
<c:when test="#{growthValue >= 0}">
|
||||
<i class="pi pi-arrow-up text-green-500 text-sm mr-2"></i>
|
||||
<span class="text-green-600 font-semibold text-sm mr-2">+#{growthValue}</span>
|
||||
<c:if test="#{not empty growthLabel}">
|
||||
<span class="text-500 text-xs">#{growthLabel}</span>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<div class="text-500 text-xs mb-2">#{noDataLabel}</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<span class="text-green-600 font-semibold text-sm mr-2">
|
||||
+#{growthValue}#{growthType == 'number' ? '' : '%'}
|
||||
</span>
|
||||
</c:when>
|
||||
<!-- Croissance négative -->
|
||||
<c:otherwise>
|
||||
<i class="pi pi-arrow-down text-red-500 text-sm mr-2"></i>
|
||||
<span class="text-red-600 font-semibold text-sm mr-2">
|
||||
#{growthValue}#{growthType == 'number' ? '' : '%'}
|
||||
</span>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<c:if test="#{not empty growthLabel}">
|
||||
<span class="text-500 text-xs">#{growthLabel}</span>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:when>
|
||||
<!-- Croissance en pourcentage (défaut) -->
|
||||
<c:otherwise>
|
||||
<c:choose>
|
||||
<c:when test="#{showGrowth and not empty growthValue and growthValue != '0' and growthValue != '0.0'}">
|
||||
<div class="flex align-items-center mb-2">
|
||||
<c:choose>
|
||||
<c:when test="#{growthValue >= 0}">
|
||||
<i class="pi pi-arrow-up text-green-500 text-sm mr-2"></i>
|
||||
<span class="text-green-600 font-semibold text-sm mr-2">+#{growthValue}%</span>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<i class="pi pi-arrow-down text-red-500 text-sm mr-2"></i>
|
||||
<span class="text-red-600 font-semibold text-sm mr-2">#{growthValue}%</span>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<c:if test="#{not empty growthLabel}">
|
||||
<span class="text-500 text-xs">#{growthLabel}</span>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<div class="text-500 text-xs mb-2">#{noDataLabel}</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<div class="text-500 text-xs mb-2">#{noDataLabel}</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
|
||||
<!-- Barre de progression -->
|
||||
<c:if test="#{showProgress and not empty progressValue}">
|
||||
<p:progressBar value="#{progressValue}"
|
||||
showValue="false"
|
||||
styleClass="surface-200"
|
||||
style="height: 0.5rem; width: 100%;" />
|
||||
<div class="mt-2">
|
||||
<p:progressBar value="#{progressValue}" showValue="false" styleClass="surface-200"
|
||||
style="height: 0.5rem; width: 100%; border-radius: 4px;" />
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,38 +1,46 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Champ Formulaire Utilisateur (WOU/DRY Pattern)
|
||||
Composant réutilisable: Champ de Formulaire Utilisateur - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Champ de formulaire générique pour utilisateur
|
||||
Version: 2.0.0
|
||||
Description: Champ de formulaire générique avec label, input, message de validation
|
||||
et texte d'aide. Supporte plusieurs types d'inputs PrimeFaces.
|
||||
Conforme au pattern WOU/DRY.
|
||||
|
||||
Paramètres:
|
||||
- id: String (requis) - ID du champ
|
||||
- id: String (requis) - ID unique du champ
|
||||
- label: String (requis) - Label du champ
|
||||
- value: Object (requis) - Valeur du champ
|
||||
- type: String (défaut: "text") - Type: "text", "email", "password", "number", "textarea", "select", "checkbox", "calendar"
|
||||
- value: Object (requis) - Valeur liée au champ (expression EL)
|
||||
- type: String (défaut: "text") - Type: "text", "email", "password", "number", "textarea",
|
||||
"select", "checkbox", "calendar", "switch"
|
||||
- required: Boolean (défaut: false) - Champ requis
|
||||
- readonly: Boolean (défaut: false) - Mode lecture seule
|
||||
- disabled: Boolean (défaut: false) - Champ désactivé
|
||||
- placeholder: String (optionnel) - Placeholder
|
||||
- helpText: String (optionnel) - Texte d'aide
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
- helpText: String (optionnel) - Texte d'aide sous le champ
|
||||
- styleClass: String (optionnel) - Classes CSS sur l'input
|
||||
- wrapperClass: String (optionnel) - Classes CSS sur le wrapper div.field
|
||||
- selectItems: List (optionnel) - Items pour select
|
||||
- rows: Number (optionnel, défaut: 3) - Nombre de lignes pour textarea
|
||||
- rows: Number (défaut: 3) - Nombre de lignes pour textarea
|
||||
- minLength: Number (optionnel) - Longueur min pour validation
|
||||
- maxLength: Number (optionnel) - Longueur max pour validation
|
||||
- pattern: String (optionnel) - Pattern regex pour validation
|
||||
- showMessage: Boolean (défaut: true) - Afficher les messages de validation
|
||||
- colSize: String (optionnel) - Taille de colonne pour layout en grille
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
1. Champ texte:
|
||||
1. Champ texte requis:
|
||||
<ui:include src="/templates/components/shared/forms/user-form-field.xhtml">
|
||||
<ui:param name="id" value="username" />
|
||||
<ui:param name="label" value="Nom d'utilisateur" />
|
||||
<ui:param name="value" value="#{user.username}" />
|
||||
<ui:param name="required" value="true" />
|
||||
<ui:param name="minLength" value="3" />
|
||||
</ui:include>
|
||||
|
||||
2. Champ email:
|
||||
@@ -42,6 +50,7 @@
|
||||
<ui:param name="value" value="#{user.email}" />
|
||||
<ui:param name="type" value="email" />
|
||||
<ui:param name="required" value="true" />
|
||||
<ui:param name="helpText" value="Doit être une adresse email valide" />
|
||||
</ui:include>
|
||||
|
||||
3. Champ select:
|
||||
@@ -50,114 +59,132 @@
|
||||
<ui:param name="label" value="Statut" />
|
||||
<ui:param name="value" value="#{user.statut}" />
|
||||
<ui:param name="type" value="select" />
|
||||
<ui:param name="selectItems" value="#{userBean.statutOptions}" />
|
||||
<ui:param name="selectItems" value="#{bean.statutOptions}" />
|
||||
</ui:include>
|
||||
|
||||
4. Toggle switch:
|
||||
<ui:include src="/templates/components/shared/forms/user-form-field.xhtml">
|
||||
<ui:param name="id" value="enabled" />
|
||||
<ui:param name="label" value="Compte activé" />
|
||||
<ui:param name="value" value="#{user.enabled}" />
|
||||
<ui:param name="type" value="switch" />
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
|
||||
<c:set var="type" value="#{empty type ? 'text' : type}" />
|
||||
<c:set var="required" value="#{empty required ? false : required}" />
|
||||
<c:set var="readonly" value="#{empty readonly ? false : readonly}" />
|
||||
<c:set var="disabled" value="#{empty disabled ? false : disabled}" />
|
||||
<c:set var="rows" value="#{empty rows ? 3 : rows}" />
|
||||
|
||||
<div class="field">
|
||||
<p:outputLabel for="#{id}" value="#{label}#{required ? ' *' : ''}" />
|
||||
|
||||
<c:set var="showMessage" value="#{empty showMessage ? true : showMessage}" />
|
||||
|
||||
<div class="field #{wrapperClass}">
|
||||
<!-- Label avec indicateur requis -->
|
||||
<c:if test="#{type != 'checkbox' and type != 'switch'}">
|
||||
<p:outputLabel for="#{id}" value="#{label}" styleClass="font-medium" />
|
||||
<c:if test="#{required}">
|
||||
<span class="text-red-500 ml-1" title="Champ obligatoire">*</span>
|
||||
</c:if>
|
||||
</c:if>
|
||||
|
||||
<c:choose>
|
||||
<!-- Champ texte -->
|
||||
<!-- Champ texte ou email -->
|
||||
<c:when test="#{type == 'text' or type == 'email'}">
|
||||
<p:inputText
|
||||
id="#{id}"
|
||||
value="#{value}"
|
||||
required="#{required}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="#{placeholder}"
|
||||
type="#{type == 'email' ? 'email' : 'text'}"
|
||||
styleClass="w-full #{styleClass}" />
|
||||
<p:inputText id="#{id}" value="#{value}" required="#{required}" readonly="#{readonly}"
|
||||
disabled="#{disabled}" placeholder="#{placeholder}" type="#{type == 'email' ? 'email' : 'text'}"
|
||||
styleClass="w-full #{styleClass}">
|
||||
<c:if test="#{not empty minLength}">
|
||||
<f:validateLength minimum="#{minLength}" />
|
||||
</c:if>
|
||||
<c:if test="#{not empty maxLength}">
|
||||
<f:validateLength maximum="#{maxLength}" />
|
||||
</c:if>
|
||||
<c:if test="#{not empty pattern}">
|
||||
<f:validateRegex pattern="#{pattern}" />
|
||||
</c:if>
|
||||
</p:inputText>
|
||||
</c:when>
|
||||
|
||||
|
||||
<!-- Champ mot de passe -->
|
||||
<c:when test="#{type == 'password'}">
|
||||
<p:password
|
||||
id="#{id}"
|
||||
value="#{value}"
|
||||
required="#{required}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="#{placeholder}"
|
||||
feedback="#{not empty feedback ? feedback : false}"
|
||||
styleClass="w-full #{styleClass}" />
|
||||
<p:password id="#{id}" value="#{value}" required="#{required}" readonly="#{readonly}"
|
||||
disabled="#{disabled}" placeholder="#{placeholder}"
|
||||
feedback="#{not empty feedback ? feedback : false}" toggleMask="true"
|
||||
styleClass="w-full #{styleClass}">
|
||||
<c:if test="#{not empty minLength}">
|
||||
<f:validateLength minimum="#{minLength}" />
|
||||
</c:if>
|
||||
<c:if test="#{not empty maxLength}">
|
||||
<f:validateLength maximum="#{maxLength}" />
|
||||
</c:if>
|
||||
</p:password>
|
||||
</c:when>
|
||||
|
||||
|
||||
<!-- Champ nombre -->
|
||||
<c:when test="#{type == 'number'}">
|
||||
<p:inputNumber
|
||||
id="#{id}"
|
||||
value="#{value}"
|
||||
required="#{required}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="#{placeholder}"
|
||||
styleClass="w-full #{styleClass}" />
|
||||
<p:inputNumber id="#{id}" value="#{value}" required="#{required}" readonly="#{readonly}"
|
||||
disabled="#{disabled}" placeholder="#{placeholder}" styleClass="w-full #{styleClass}" />
|
||||
</c:when>
|
||||
|
||||
|
||||
<!-- Champ textarea -->
|
||||
<c:when test="#{type == 'textarea'}">
|
||||
<p:inputTextarea
|
||||
id="#{id}"
|
||||
value="#{value}"
|
||||
required="#{required}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="#{placeholder}"
|
||||
rows="#{rows}"
|
||||
styleClass="w-full #{styleClass}" />
|
||||
<p:inputTextarea id="#{id}" value="#{value}" required="#{required}" readonly="#{readonly}"
|
||||
disabled="#{disabled}" placeholder="#{placeholder}" rows="#{rows}" autoResize="true"
|
||||
styleClass="w-full #{styleClass}">
|
||||
<c:if test="#{not empty maxLength}">
|
||||
<f:validateLength maximum="#{maxLength}" />
|
||||
</c:if>
|
||||
</p:inputTextarea>
|
||||
</c:when>
|
||||
|
||||
|
||||
<!-- Champ select -->
|
||||
<c:when test="#{type == 'select'}">
|
||||
<p:selectOneMenu
|
||||
id="#{id}"
|
||||
value="#{value}"
|
||||
required="#{required}"
|
||||
readonly="#{readonly}"
|
||||
styleClass="w-full #{styleClass}">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<p:selectOneMenu id="#{id}" value="#{value}" required="#{required}" readonly="#{readonly}"
|
||||
disabled="#{disabled}" styleClass="w-full #{styleClass}">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{selectItems}" />
|
||||
</p:selectOneMenu>
|
||||
</c:when>
|
||||
|
||||
|
||||
<!-- Champ checkbox -->
|
||||
<c:when test="#{type == 'checkbox'}">
|
||||
<p:selectBooleanCheckbox
|
||||
id="#{id}"
|
||||
value="#{value}"
|
||||
readonly="#{readonly}" />
|
||||
<div class="flex align-items-center gap-2">
|
||||
<p:selectBooleanCheckbox id="#{id}" value="#{value}" readonly="#{readonly}"
|
||||
disabled="#{disabled}" />
|
||||
<p:outputLabel for="#{id}" value="#{label}" styleClass="font-medium" />
|
||||
</div>
|
||||
</c:when>
|
||||
|
||||
|
||||
<!-- Toggle switch -->
|
||||
<c:when test="#{type == 'switch'}">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<p:toggleSwitch id="#{id}" value="#{value}" readonly="#{readonly}" disabled="#{disabled}" />
|
||||
<p:outputLabel for="#{id}" value="#{label}" styleClass="font-medium" />
|
||||
</div>
|
||||
</c:when>
|
||||
|
||||
<!-- Champ calendar -->
|
||||
<c:when test="#{type == 'calendar'}">
|
||||
<p:calendar
|
||||
id="#{id}"
|
||||
value="#{value}"
|
||||
required="#{required}"
|
||||
readonly="#{readonly}"
|
||||
pattern="dd/MM/yyyy"
|
||||
styleClass="w-full #{styleClass}" />
|
||||
<p:datePicker id="#{id}" value="#{value}" required="#{required}" readonly="#{readonly}"
|
||||
disabled="#{disabled}" pattern="dd/MM/yyyy" showIcon="true" styleClass="w-full #{styleClass}" />
|
||||
</c:when>
|
||||
|
||||
|
||||
<!-- Par défaut: champ texte -->
|
||||
<c:otherwise>
|
||||
<p:inputText
|
||||
id="#{id}"
|
||||
value="#{value}"
|
||||
required="#{required}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="#{placeholder}"
|
||||
styleClass="w-full #{styleClass}" />
|
||||
<p:inputText id="#{id}" value="#{value}" required="#{required}" readonly="#{readonly}"
|
||||
disabled="#{disabled}" placeholder="#{placeholder}" styleClass="w-full #{styleClass}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
|
||||
<!-- Message de validation -->
|
||||
<c:if test="#{showMessage}">
|
||||
<p:message for="#{id}" display="text" styleClass="mt-1" />
|
||||
</c:if>
|
||||
|
||||
<!-- Texte d'aide -->
|
||||
<c:if test="#{not empty helpText}">
|
||||
<small class="text-color-secondary text-xs">#{helpText}</small>
|
||||
<small class="text-color-secondary block mt-1">#{helpText}</small>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,22 +1,20 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
xmlns:lum="http://xmlns.jcp.org/jsf/composite/components">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Tableau Utilisateurs (WOU/DRY Pattern)
|
||||
Composant réutilisable: Tableau de Données Utilisateurs - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Tableau de données pour afficher une liste d'utilisateurs
|
||||
Version: 2.0.0
|
||||
Description: Tableau de données PrimeFaces pour afficher et gérer une liste
|
||||
d'utilisateurs avec colonnes configurables, tri, pagination,
|
||||
sélection et actions.
|
||||
|
||||
Paramètres:
|
||||
- users: List<UserDTO> (requis) - Liste des utilisateurs
|
||||
- users: List<UserDTO> (requis) - Liste des utilisateurs
|
||||
- var: String (défaut: "user") - Nom de la variable pour itération
|
||||
- tableId: String (défaut: "userTable") - ID du tableau
|
||||
- tableId: String (défaut: "userTable") - ID unique du tableau
|
||||
- paginator: Boolean (défaut: true) - Activer la pagination
|
||||
- rows: Number (défaut: 20) - Nombre de lignes par page
|
||||
- showActions: Boolean (défaut: true) - Afficher la colonne actions
|
||||
@@ -24,14 +22,19 @@
|
||||
- showEmail: Boolean (défaut: true) - Afficher la colonne email
|
||||
- showStatus: Boolean (défaut: true) - Afficher la colonne statut
|
||||
- showSelection: Boolean (défaut: false) - Activer la sélection
|
||||
- selection: UserDTO (optionnel) - Utilisateur sélectionné
|
||||
- selectionMode: String (défaut: "single") - Mode: "single" ou "multiple"
|
||||
- totalRecords: Long (optionnel) - Nombre total d'enregistrements pour l'affichage
|
||||
- hasOnPageChange: Boolean (défaut: false) - Indique si un gestionnaire de pagination est fourni
|
||||
- onPageChange: MethodExpression (optionnel) - Méthode à appeler lors du changement de page (requis si hasOnPageChange=true)
|
||||
- lazy: Boolean (défaut: false) - Activer le chargement paresseux
|
||||
- update: String (optionnel) - Composants à mettre à jour
|
||||
- selection: UserDTO (optionnel) - Utilisateur(s) sélectionné(s)
|
||||
- selectionMode: String (défaut: "single") - "single" ou "multiple"
|
||||
- totalRecords: Long (optionnel) - Nombre total d'enregistrements
|
||||
- hasOnPageChange: Boolean (défaut: false) - Si un listener de pagination est fourni
|
||||
- onPageChange: MethodExpression (optionnel) - Listener de changement de page
|
||||
- lazy: Boolean (défaut: false) - Chargement paresseux
|
||||
- activateAction: MethodExpression (optionnel) - Action pour activer un utilisateur
|
||||
- deactivateAction: MethodExpression (optionnel) - Action pour désactiver un utilisateur
|
||||
- deleteAction: MethodExpression (optionnel) - Action pour supprimer un utilisateur
|
||||
- update: String (optionnel) - Composants à mettre à jour après action
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
- headerTitle: String (défaut: "Utilisateurs") - Titre du tableau
|
||||
- emptyMessage: String (défaut: "Aucun utilisateur trouvé") - Message quand vide
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
@@ -40,14 +43,17 @@
|
||||
<ui:param name="users" value="#{userBean.users}" />
|
||||
</ui:include>
|
||||
|
||||
2. Tableau avec sélection:
|
||||
2. Tableau complet avec actions:
|
||||
<ui:include src="/templates/components/shared/tables/user-data-table.xhtml">
|
||||
<ui:param name="users" value="#{userBean.users}" />
|
||||
<ui:param name="showSelection" value="true" />
|
||||
<ui:param name="selection" value="#{userBean.selectedUser}" />
|
||||
<ui:param name="totalRecords" value="#{userBean.totalUsers}" />
|
||||
<ui:param name="activateAction" value="#{userBean.activateUser}" />
|
||||
<ui:param name="deactivateAction" value="#{userBean.deactivateUser}" />
|
||||
<ui:param name="deleteAction" value="#{userBean.deleteUser}" />
|
||||
<ui:param name="update" value="@form" />
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
|
||||
<c:set var="varName" value="#{empty var ? 'user' : var}" />
|
||||
<c:set var="tableId" value="#{empty tableId ? 'userTable' : tableId}" />
|
||||
<c:set var="paginator" value="#{empty paginator ? true : paginator}" />
|
||||
@@ -59,133 +65,134 @@
|
||||
<c:set var="showSelection" value="#{empty showSelection ? false : showSelection}" />
|
||||
<c:set var="selectionMode" value="#{empty selectionMode ? 'single' : selectionMode}" />
|
||||
<c:set var="hasOnPageChange" value="#{empty hasOnPageChange ? false : hasOnPageChange}" />
|
||||
|
||||
<p:dataTable
|
||||
id="#{tableId}"
|
||||
value="#{users}"
|
||||
var="user"
|
||||
rowKey="#{user.id}"
|
||||
paginator="#{paginator}"
|
||||
rows="#{rows}"
|
||||
<c:set var="headerTitle" value="#{empty headerTitle ? 'Utilisateurs' : headerTitle}" />
|
||||
<c:set var="emptyMessage" value="#{empty emptyMessage ? 'Aucun utilisateur trouvé' : emptyMessage}" />
|
||||
|
||||
<p:dataTable id="#{tableId}" value="#{users}" var="user" rowKey="#{user.id}" paginator="#{paginator}" rows="#{rows}"
|
||||
rowCount="#{not empty totalRecords ? totalRecords : (users != null ? users.size() : 0)}"
|
||||
selection="#{selection}"
|
||||
selectionMode="#{selectionMode}"
|
||||
selection="#{selection}" selectionMode="#{showSelection ? selectionMode : ''}"
|
||||
styleClass="p-datatable-sm p-datatable-gridlines p-datatable-striped w-full #{styleClass}"
|
||||
rowStyleClass="p-datatable-row"
|
||||
widgetVar="#{tableId}Widget"
|
||||
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
|
||||
rowsPerPageTemplate="10,20,50,100"
|
||||
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
|
||||
emptyMessage="Aucun utilisateur trouvé"
|
||||
reflow="true"
|
||||
responsiveLayout="scroll"
|
||||
lazy="#{not empty lazy and lazy}">
|
||||
|
||||
emptyMessage="#{emptyMessage}" reflow="true" responsiveLayout="scroll" lazy="#{not empty lazy and lazy}"
|
||||
sortMode="multiple" resizableColumns="true" scrollable="true">
|
||||
|
||||
<f:facet name="header">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-900 font-semibold text-xl">Utilisateurs</span>
|
||||
<c:if test="#{not empty totalRecords}">
|
||||
<span class="text-600 text-sm">Total: #{totalRecords}</span>
|
||||
</c:if>
|
||||
<div class="flex align-items-center justify-content-between flex-wrap gap-2">
|
||||
<span class="text-900 font-semibold text-xl">
|
||||
<i class="pi pi-users mr-2"></i>#{headerTitle}
|
||||
</span>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<c:if test="#{not empty totalRecords}">
|
||||
<p:tag value="#{totalRecords} résultats" severity="info" styleClass="text-xs" />
|
||||
</c:if>
|
||||
</div>
|
||||
</div>
|
||||
</f:facet>
|
||||
|
||||
|
||||
<!-- Gestionnaire d'événements pour la pagination -->
|
||||
<c:if test="#{hasOnPageChange}">
|
||||
<p:ajax event="page" listener="#{onPageChange}" update="#{not empty update ? update : tableId}" />
|
||||
</c:if>
|
||||
|
||||
|
||||
<!-- Colonne de sélection -->
|
||||
<c:if test="#{showSelection}">
|
||||
<p:column selectionMode="#{selectionMode}" style="width: 50px" />
|
||||
<p:column selectionMode="#{selectionMode}" style="width: 50px" exportable="false" />
|
||||
</c:if>
|
||||
|
||||
<!-- Colonne Username -->
|
||||
<p:column headerText="Nom d'utilisateur" sortBy="#{user.username}" style="width: 200px">
|
||||
<div class="flex align-items-center py-2">
|
||||
<div class="border-circle overflow-hidden mr-2 flex-shrink-0" style="width: 36px; height: 36px;">
|
||||
<div class="bg-primary text-white flex align-items-center justify-content-center w-full h-full">
|
||||
<span style="font-size: 0.875rem; font-weight: bold;">
|
||||
#{user.prenom != null ? user.prenom.substring(0,1) : 'U'}#{user.nom != null ? user.nom.substring(0,1) : ''}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Colonne Username avec avatar -->
|
||||
<p:column headerText="Nom d'utilisateur" sortBy="#{user.username}" filterBy="#{user.username}"
|
||||
filterMatchMode="contains" style="min-width: 200px">
|
||||
<div class="flex align-items-center gap-2 py-1">
|
||||
<div class="border-circle overflow-hidden flex-shrink-0 flex align-items-center justify-content-center"
|
||||
style="width: 32px; height: 32px; background-color: var(--primary-color); color: var(--primary-color-text);">
|
||||
<span style="font-size: 0.75rem; font-weight: 600;">
|
||||
#{user.prenom != null and user.prenom.length() > 0 ? user.prenom.substring(0,1) : 'U'}#{user.nom
|
||||
!= null and user.nom.length() > 0 ? user.nom.substring(0,1) : ''}
|
||||
</span>
|
||||
</div>
|
||||
<span class="font-semibold text-900">#{user.username}</span>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
|
||||
<!-- Colonne Nom complet -->
|
||||
<p:column headerText="Nom complet" sortBy="#{user.nom}" style="width: 220px">
|
||||
<div class="flex flex-column py-2">
|
||||
<p:column headerText="Nom complet" sortBy="#{user.nom}" filterBy="#{user.nom}" filterMatchMode="contains"
|
||||
style="min-width: 200px">
|
||||
<div class="flex flex-column py-1">
|
||||
<span class="font-medium text-900">#{user.prenom} #{user.nom}</span>
|
||||
<c:if test="#{not empty user.fonction}">
|
||||
<small class="text-600 text-xs mt-1">#{user.fonction}</small>
|
||||
</c:if>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
|
||||
<!-- Colonne Email -->
|
||||
<c:if test="#{showEmail}">
|
||||
<p:column headerText="Email" sortBy="#{user.email}" style="width: 250px">
|
||||
<div class="flex align-items-center py-2">
|
||||
<i class="pi pi-envelope text-500 mr-2"></i>
|
||||
<span class="text-900">#{user.email}</span>
|
||||
<p:column headerText="Email" sortBy="#{user.email}" filterBy="#{user.email}" filterMatchMode="contains"
|
||||
style="min-width: 250px">
|
||||
<div class="flex align-items-center gap-2 py-1">
|
||||
<i class="pi pi-envelope text-400 text-sm"></i>
|
||||
<span class="text-900 text-sm">#{user.email}</span>
|
||||
<c:if test="#{user.emailVerified}">
|
||||
<i class="pi pi-check-circle text-green-500 ml-2" title="Email vérifié"></i>
|
||||
<i class="pi pi-verified text-green-500 text-xs" title="Email vérifié"></i>
|
||||
</c:if>
|
||||
</div>
|
||||
</p:column>
|
||||
</c:if>
|
||||
|
||||
|
||||
<!-- Colonne Statut -->
|
||||
<c:if test="#{showStatus}">
|
||||
<p:column headerText="Statut" sortBy="#{user.statut}" style="width: 130px">
|
||||
<div class="flex align-items-center py-2">
|
||||
<p:tag
|
||||
value="#{user.statut != null ? user.statut : 'INCONNU'}"
|
||||
severity="#{user.enabled ? 'success' : 'danger'}" />
|
||||
<p:column headerText="Statut" sortBy="#{user.enabled}" style="width: 120px" exportable="true">
|
||||
<div class="flex align-items-center justify-content-center py-1">
|
||||
<p:tag value="#{user.enabled ? 'Actif' : 'Inactif'}"
|
||||
severity="#{user.enabled ? 'success' : 'danger'}"
|
||||
icon="#{user.enabled ? 'pi pi-check' : 'pi pi-times'}" />
|
||||
</div>
|
||||
</p:column>
|
||||
</c:if>
|
||||
|
||||
|
||||
<!-- Colonne Rôles -->
|
||||
<c:if test="#{showRoles}">
|
||||
<p:column headerText="Rôles" style="width: 200px">
|
||||
<div class="flex flex-wrap gap-1 py-2 align-items-center">
|
||||
<p:column headerText="Rôles" style="min-width: 180px" exportable="false">
|
||||
<div class="flex flex-wrap gap-1 py-1 align-items-center">
|
||||
<c:choose>
|
||||
<c:when test="#{user.realmRoles != null and !user.realmRoles.isEmpty()}">
|
||||
<c:forEach var="role" items="#{user.realmRoles}" varStatus="status">
|
||||
<c:if test="#{status.index < 3}">
|
||||
<c:if test="#{status.index lt 3}">
|
||||
<p:tag value="#{role}" severity="info" styleClass="text-xs" />
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
<c:if test="#{user.realmRoles.size() > 3}">
|
||||
<p:tag value="+#{user.realmRoles.size() - 3}" severity="secondary" styleClass="text-xs" />
|
||||
<c:if test="#{user.realmRoles.size() gt 3}">
|
||||
<p:tag value="+#{user.realmRoles.size() - 3}" severity="secondary" styleClass="text-xs"
|
||||
title="#{user.realmRoles.size() - 3} rôle(s) supplémentaire(s)" />
|
||||
</c:if>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<span class="text-500 text-xs">Aucun rôle</span>
|
||||
<span class="text-400 text-xs font-italic">Aucun rôle</span>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
</p:column>
|
||||
</c:if>
|
||||
|
||||
|
||||
<!-- Colonne Actions -->
|
||||
<c:if test="#{showActions}">
|
||||
<p:column headerText="Actions" style="width: 100px" exportable="false">
|
||||
<div class="flex justify-content-center align-items-center" style="min-height: 3rem;">
|
||||
<lum:user-action-dropdown
|
||||
userId="#{user.id}"
|
||||
userEnabled="#{user.enabled}"
|
||||
update="#{not empty update ? update : tableId}"
|
||||
activateAction="#{activateAction}"
|
||||
deactivateAction="#{deactivateAction}"
|
||||
deleteAction="#{deleteAction}" />
|
||||
<p:column headerText="Actions" style="width: 80px" exportable="false">
|
||||
<div class="flex justify-content-center align-items-center" style="min-height: 2.5rem;">
|
||||
<ui:include src="/templates/components/user-management/user-actions.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="layout" value="dropdown" />
|
||||
<ui:param name="update" value="#{not empty update ? update : tableId}" />
|
||||
<ui:param name="actionBean" value="#{actionBean}" />
|
||||
<ui:param name="activateAction" value="#{activateAction}" />
|
||||
<ui:param name="deactivateAction" value="#{deactivateAction}" />
|
||||
<ui:param name="deleteAction" value="#{deleteAction}" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</p:column>
|
||||
</c:if>
|
||||
</p:dataTable>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,61 +1,16 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Actions Utilisateur (WOU/DRY Pattern)
|
||||
Composant réutilisable: Actions Utilisateur - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Boutons d'action pour un utilisateur (activate, deactivate, delete, etc.)
|
||||
|
||||
Paramètres:
|
||||
- user: UserDTO (requis) - L'utilisateur concerné
|
||||
- showView: Boolean (défaut: true) - Afficher le bouton "Voir"
|
||||
- showEdit: Boolean (défaut: true) - Afficher le bouton "Modifier"
|
||||
- showDelete: Boolean (défaut: true) - Afficher le bouton "Supprimer"
|
||||
- showActivate: Boolean (défaut: true) - Afficher le bouton "Activer"
|
||||
- showDeactivate: Boolean (défaut: true) - Afficher le bouton "Désactiver"
|
||||
- showResetPassword: Boolean (défaut: true) - Afficher le bouton "Réinitialiser mot de passe"
|
||||
- showLogoutSessions: Boolean (défaut: false) - Afficher le bouton "Déconnecter toutes les sessions"
|
||||
- viewAction: String (optionnel) - Action pour "Voir"
|
||||
- editAction: String (optionnel) - Action pour "Modifier"
|
||||
- deleteAction: String (optionnel) - Action pour "Supprimer"
|
||||
- activateAction: String (optionnel) - Action pour "Activer"
|
||||
- deactivateAction: String (optionnel) - Action pour "Désactiver"
|
||||
- resetPasswordAction: String (optionnel) - Action pour "Réinitialiser mot de passe"
|
||||
- logoutSessionsAction: String (optionnel) - Action pour "Déconnecter sessions"
|
||||
- viewOutcome: String (optionnel) - Page pour "Voir"
|
||||
- editOutcome: String (optionnel) - Page pour "Modifier"
|
||||
- update: String (défaut: "@form") - Composants à mettre à jour
|
||||
- layout: String (défaut: "horizontal") - Layout: "horizontal" ou "vertical" ou "dropdown"
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
1. Actions horizontales (défaut):
|
||||
<ui:include src="/templates/components/user-management/user-actions.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="update" value="userTable" />
|
||||
</ui:include>
|
||||
|
||||
2. Actions en dropdown:
|
||||
<ui:include src="/templates/components/user-management/user-actions.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="layout" value="dropdown" />
|
||||
<ui:param name="update" value="userTable" />
|
||||
</ui:include>
|
||||
|
||||
3. Actions limitées:
|
||||
<ui:include src="/templates/components/user-management/user-actions.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="showDelete" value="false" />
|
||||
<ui:param name="showResetPassword" value="false" />
|
||||
</ui:include>
|
||||
Version: 2.1.0
|
||||
Description: Ensemble de boutons d'action pour un utilisateur.
|
||||
Utilise le confirmDialog global pour les suppressions.
|
||||
-->
|
||||
|
||||
|
||||
<c:set var="update" value="#{empty update ? '@form' : update}" />
|
||||
<c:set var="layout" value="#{empty layout ? 'horizontal' : layout}" />
|
||||
<c:set var="showView" value="#{empty showView ? true : showView}" />
|
||||
@@ -65,323 +20,177 @@
|
||||
<c:set var="showDeactivate" value="#{empty showDeactivate ? true : showDeactivate}" />
|
||||
<c:set var="showResetPassword" value="#{empty showResetPassword ? true : showResetPassword}" />
|
||||
<c:set var="showLogoutSessions" value="#{empty showLogoutSessions ? false : showLogoutSessions}" />
|
||||
|
||||
<!-- Définir les actions par défaut si non fournies -->
|
||||
<c:set var="defaultActivateAction" value="#{userBean.activateUser(user.id)}" />
|
||||
<c:set var="defaultDeactivateAction" value="#{userBean.deactivateUser(user.id)}" />
|
||||
<c:set var="defaultDeleteAction" value="#{userBean.deleteUser(user.id)}" />
|
||||
<c:set var="defaultResetPasswordAction" value="#{userBean.resetPassword(user.id)}" />
|
||||
<c:set var="defaultLogoutSessionsAction" value="#{userBean.logoutAllSessions(user.id)}" />
|
||||
|
||||
<c:set var="iconOnly" value="#{empty iconOnly ? true : iconOnly}" />
|
||||
|
||||
<!-- Bean Cible pour les actions (par défaut userBean) -->
|
||||
<c:set var="targetBean" value="#{empty actionBean ? userBean : actionBean}" />
|
||||
|
||||
<!-- Actions par défaut -->
|
||||
<c:set var="actActivate" value="#{empty activateAction ? targetBean.activateUser(user.id) : activateAction}" />
|
||||
<c:set var="actDeactivate"
|
||||
value="#{empty deactivateAction ? targetBean.deactivateUser(user.id) : deactivateAction}" />
|
||||
<c:set var="actDelete" value="#{empty deleteAction ? targetBean.deleteUser(user.id) : deleteAction}" />
|
||||
<c:set var="actReset"
|
||||
value="#{empty resetPasswordAction ? targetBean.resetPassword(user.id) : resetPasswordAction}" />
|
||||
<c:set var="actLogout"
|
||||
value="#{empty logoutSessionsAction ? targetBean.logoutAllSessions(user.id) : logoutSessionsAction}" />
|
||||
|
||||
<c:choose>
|
||||
<!-- Layout Dropdown -->
|
||||
<c:when test="#{layout == 'dropdown'}">
|
||||
<p:commandButton
|
||||
icon="pi pi-ellipsis-v"
|
||||
styleClass="p-button-text p-button-sm p-button-rounded p-button-plain"
|
||||
type="button"
|
||||
title="Actions"
|
||||
style="width: 2rem; height: 2rem; padding: 0; margin: 0;">
|
||||
<p:commandButton icon="pi pi-ellipsis-v"
|
||||
styleClass="p-button-text p-button-sm p-button-rounded p-button-plain" type="button" title="Actions"
|
||||
style="width: 2rem; height: 2rem;">
|
||||
<p:menu styleClass="w-12rem">
|
||||
<c:if test="#{showView}">
|
||||
<p:menuitem
|
||||
value="Voir le profil"
|
||||
icon="pi pi-eye"
|
||||
<p:menuitem value="Voir le profil" icon="pi pi-eye"
|
||||
outcome="#{not empty viewOutcome ? viewOutcome : '/pages/user-manager/users/profile'}">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
</p:menuitem>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{showEdit}">
|
||||
<p:menuitem
|
||||
value="Modifier"
|
||||
icon="pi pi-pencil"
|
||||
<p:menuitem value="Modifier" icon="pi pi-pencil"
|
||||
outcome="#{not empty editOutcome ? editOutcome : '/pages/user-manager/users/edit'}">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
</p:menuitem>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{showResetPassword}">
|
||||
<p:menuitem
|
||||
value="Réinitialiser mot de passe"
|
||||
icon="pi pi-key"
|
||||
onclick="PF('resetPasswordDialog').show()" />
|
||||
<p:menuitem value="Réinitialiser MDP" icon="pi pi-key"
|
||||
onclick="PF('resetPasswordDialog_#{user.id}').show()" />
|
||||
</c:if>
|
||||
|
||||
<p:separator />
|
||||
|
||||
<c:if test="#{showActivate and not user.enabled}">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty activateAction}">
|
||||
<p:menuitem
|
||||
value="Activer"
|
||||
icon="pi pi-check"
|
||||
styleClass="text-green-600"
|
||||
action="#{activateAction}"
|
||||
update="#{update}" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:menuitem
|
||||
value="Activer"
|
||||
icon="pi pi-check"
|
||||
styleClass="text-green-600"
|
||||
action="#{userBean.activateUser(user.id)}"
|
||||
update="#{update}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<p:menuitem value="Activer" icon="pi pi-check" styleClass="text-green-600"
|
||||
action="#{actActivate}" update="#{update}" />
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{showDeactivate and user.enabled}">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty deactivateAction}">
|
||||
<p:menuitem
|
||||
value="Désactiver"
|
||||
icon="pi pi-times"
|
||||
styleClass="text-orange-600"
|
||||
action="#{deactivateAction}"
|
||||
update="#{update}" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:menuitem
|
||||
value="Désactiver"
|
||||
icon="pi pi-times"
|
||||
styleClass="text-orange-600"
|
||||
action="#{userBean.deactivateUser(user.id)}"
|
||||
update="#{update}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<p:menuitem value="Désactiver" icon="pi pi-times" styleClass="text-orange-600"
|
||||
action="#{actDeactivate}" update="#{update}" />
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{showLogoutSessions}">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty logoutSessionsAction}">
|
||||
<p:menuitem
|
||||
value="Déconnecter toutes les sessions"
|
||||
icon="pi pi-sign-out"
|
||||
styleClass="text-blue-600"
|
||||
action="#{logoutSessionsAction}"
|
||||
update="#{update}" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:menuitem
|
||||
value="Déconnecter toutes les sessions"
|
||||
icon="pi pi-sign-out"
|
||||
styleClass="text-blue-600"
|
||||
action="#{userBean.logoutAllSessions(user.id)}"
|
||||
update="#{update}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<p:menuitem value="Déconnecter sessions" icon="pi pi-sign-out" styleClass="text-blue-600"
|
||||
action="#{actLogout}" update="#{update}" />
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{showDelete}">
|
||||
<p:separator />
|
||||
<p:menuitem
|
||||
value="Supprimer"
|
||||
icon="pi pi-trash"
|
||||
styleClass="text-red-600"
|
||||
onclick="PF('confirmDeleteDialog').show()" />
|
||||
<p:menuitem value="Supprimer" icon="pi pi-trash" styleClass="text-red-600" action="#{actDelete}"
|
||||
update="#{update}">
|
||||
<p:confirm header="Confirmation" message="Supprimer l'utilisateur #{user.username} ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:menuitem>
|
||||
</c:if>
|
||||
</p:menu>
|
||||
</p:commandButton>
|
||||
</c:when>
|
||||
|
||||
<!-- Layout Horizontal (défaut) -->
|
||||
|
||||
<!-- Layout Horizontal -->
|
||||
<c:otherwise>
|
||||
<div class="flex gap-1">
|
||||
<div class="flex gap-1 align-items-center">
|
||||
<c:if test="#{showView}">
|
||||
<p:commandButton
|
||||
icon="pi pi-eye"
|
||||
title="Voir le profil"
|
||||
styleClass="p-button-text p-button-sm p-button-info"
|
||||
outcome="#{not empty viewOutcome ? viewOutcome : '/pages/user-manager/users/profile'}">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
</p:commandButton>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="icon" value="pi pi-eye" />
|
||||
<ui:param name="iconOnly" value="true" />
|
||||
<ui:param name="severity" value="info" />
|
||||
<ui:param name="title" value="Voir" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="hasOutcome" value="true" />
|
||||
<ui:param name="outcome"
|
||||
value="#{not empty viewOutcome ? viewOutcome : '/pages/user-manager/users/profile'}" />
|
||||
<ui:param name="paramUserId" value="#{user.id}" />
|
||||
<ui:param name="paramRealm" value="#{user.realmName}" />
|
||||
</ui:include>
|
||||
</c:if>
|
||||
|
||||
|
||||
<c:if test="#{showEdit}">
|
||||
<p:commandButton
|
||||
icon="pi pi-pencil"
|
||||
title="Modifier"
|
||||
styleClass="p-button-text p-button-sm p-button-warning"
|
||||
outcome="#{not empty editOutcome ? editOutcome : '/pages/user-manager/users/edit'}">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
</p:commandButton>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="icon" value="pi pi-pencil" />
|
||||
<ui:param name="iconOnly" value="true" />
|
||||
<ui:param name="severity" value="warning" />
|
||||
<ui:param name="title" value="Modifier" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="hasOutcome" value="true" />
|
||||
<ui:param name="outcome"
|
||||
value="#{not empty editOutcome ? editOutcome : '/pages/user-manager/users/edit'}" />
|
||||
<ui:param name="paramUserId" value="#{user.id}" />
|
||||
<ui:param name="paramRealm" value="#{user.realmName}" />
|
||||
</ui:include>
|
||||
</c:if>
|
||||
|
||||
|
||||
<c:if test="#{showResetPassword}">
|
||||
<p:commandButton
|
||||
icon="pi pi-key"
|
||||
title="Réinitialiser mot de passe"
|
||||
styleClass="p-button-text p-button-sm p-button-help"
|
||||
onclick="PF('resetPasswordDialog').show()" />
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="icon" value="pi pi-key" />
|
||||
<ui:param name="iconOnly" value="true" />
|
||||
<ui:param name="severity" value="help" />
|
||||
<ui:param name="title" value="Réinitialiser MDP" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="onclick" value="PF('resetPasswordDialog_#{user.id}').show()" />
|
||||
</ui:include>
|
||||
</c:if>
|
||||
|
||||
|
||||
<c:if test="#{showActivate and not user.enabled}">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty activateAction}">
|
||||
<p:commandButton
|
||||
icon="pi pi-check"
|
||||
title="Activer"
|
||||
styleClass="p-button-text p-button-sm p-button-success"
|
||||
action="#{activateAction}"
|
||||
update="#{update}" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:commandButton
|
||||
icon="pi pi-check"
|
||||
title="Activer"
|
||||
styleClass="p-button-text p-button-sm p-button-success"
|
||||
action="#{userBean.activateUser(user.id)}"
|
||||
update="#{update}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="icon" value="pi pi-check" />
|
||||
<ui:param name="iconOnly" value="true" />
|
||||
<ui:param name="severity" value="success" />
|
||||
<ui:param name="title" value="Activer" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="hasAction" value="true" />
|
||||
<ui:param name="action" value="#{actActivate}" />
|
||||
<ui:param name="update" value="#{update}" />
|
||||
</ui:include>
|
||||
</c:if>
|
||||
|
||||
|
||||
<c:if test="#{showDeactivate and user.enabled}">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty deactivateAction}">
|
||||
<p:commandButton
|
||||
icon="pi pi-times"
|
||||
title="Désactiver"
|
||||
styleClass="p-button-text p-button-sm p-button-warning"
|
||||
action="#{deactivateAction}"
|
||||
update="#{update}" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:commandButton
|
||||
icon="pi pi-times"
|
||||
title="Désactiver"
|
||||
styleClass="p-button-text p-button-sm p-button-warning"
|
||||
action="#{userBean.deactivateUser(user.id)}"
|
||||
update="#{update}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="icon" value="pi pi-times" />
|
||||
<ui:param name="iconOnly" value="true" />
|
||||
<ui:param name="severity" value="warning" />
|
||||
<ui:param name="title" value="Désactiver" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="hasAction" value="true" />
|
||||
<ui:param name="action" value="#{actDeactivate}" />
|
||||
<ui:param name="update" value="#{update}" />
|
||||
</ui:include>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{showLogoutSessions}">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty logoutSessionsAction}">
|
||||
<p:commandButton
|
||||
icon="pi pi-sign-out"
|
||||
title="Déconnecter toutes les sessions"
|
||||
styleClass="p-button-text p-button-sm p-button-info"
|
||||
action="#{logoutSessionsAction}"
|
||||
update="#{update}" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:commandButton
|
||||
icon="pi pi-sign-out"
|
||||
title="Déconnecter toutes les sessions"
|
||||
styleClass="p-button-text p-button-sm p-button-info"
|
||||
action="#{userBean.logoutAllSessions(user.id)}"
|
||||
update="#{update}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if>
|
||||
|
||||
|
||||
<c:if test="#{showDelete}">
|
||||
<p:commandButton
|
||||
icon="pi pi-trash"
|
||||
title="Supprimer"
|
||||
styleClass="p-button-text p-button-sm p-button-danger"
|
||||
onclick="PF('confirmDeleteDialog').show()" />
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="icon" value="pi pi-trash" />
|
||||
<ui:param name="iconOnly" value="true" />
|
||||
<ui:param name="severity" value="danger" />
|
||||
<ui:param name="title" value="Supprimer" />
|
||||
<ui:param name="rounded" value="true" />
|
||||
<ui:param name="hasAction" value="true" />
|
||||
<ui:param name="action" value="#{actDelete}" />
|
||||
<ui:param name="update" value="#{update}" />
|
||||
<ui:param name="confirmMessage" value="Supprimer l'utilisateur #{user.username} ?" />
|
||||
</ui:include>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
<!-- Dialog de confirmation de suppression -->
|
||||
<p:confirmDialog
|
||||
id="confirmDeleteDialog"
|
||||
widgetVar="confirmDeleteDialog"
|
||||
message="Êtes-vous sûr de vouloir supprimer l'utilisateur #{user.username} ?"
|
||||
header="Confirmation de suppression"
|
||||
severity="warn">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty deleteAction}">
|
||||
<p:commandButton
|
||||
value="Oui"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-danger"
|
||||
action="#{deleteAction}"
|
||||
update="#{update}"
|
||||
oncomplete="PF('confirmDeleteDialog').hide()" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:commandButton
|
||||
value="Oui"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-danger"
|
||||
action="#{userBean.deleteUser(user.id)}"
|
||||
update="#{update}"
|
||||
oncomplete="PF('confirmDeleteDialog').hide()" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<p:commandButton
|
||||
value="Non"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
onclick="PF('confirmDeleteDialog').hide()" />
|
||||
</p:confirmDialog>
|
||||
|
||||
<!-- Dialog de réinitialisation de mot de passe -->
|
||||
<p:dialog
|
||||
id="resetPasswordDialog"
|
||||
widgetVar="resetPasswordDialog"
|
||||
header="Réinitialiser le mot de passe"
|
||||
modal="true"
|
||||
styleClass="w-full md:w-4">
|
||||
|
||||
<!-- Dialog de Reset Password (reste spécifique par user car il contient un form) -->
|
||||
<p:dialog id="resetPasswordDialog_#{user.id}" widgetVar="resetPasswordDialog_#{user.id}"
|
||||
header="Réinitialiser le mot de passe" modal="true" styleClass="w-full md:w-4">
|
||||
<h:form>
|
||||
<p:panelGrid columns="2" styleClass="w-full">
|
||||
<p:outputLabel for="newPassword" value="Nouveau mot de passe *" />
|
||||
<p:password id="newPassword"
|
||||
value="#{userBean.newPassword}"
|
||||
feedback="true"
|
||||
required="true"
|
||||
styleClass="w-full">
|
||||
<f:validateLength minimum="8" maximum="100" />
|
||||
</p:password>
|
||||
|
||||
<p:outputLabel for="newPasswordConfirm" value="Confirmer *" />
|
||||
<p:password id="newPasswordConfirm"
|
||||
value="#{userBean.newPasswordConfirm}"
|
||||
required="true"
|
||||
styleClass="w-full" />
|
||||
</p:panelGrid>
|
||||
|
||||
<f:facet name="footer">
|
||||
<div class="flex gap-2 justify-content-end">
|
||||
<c:choose>
|
||||
<c:when test="#{not empty resetPasswordAction}">
|
||||
<p:commandButton
|
||||
value="Réinitialiser"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-primary"
|
||||
action="#{resetPasswordAction}"
|
||||
update="#{update}"
|
||||
oncomplete="PF('resetPasswordDialog').hide()"
|
||||
process="@form" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:commandButton
|
||||
value="Réinitialiser"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-primary"
|
||||
action="#{userBean.resetPassword(user.id)}"
|
||||
update="#{update}"
|
||||
oncomplete="PF('resetPasswordDialog').hide()"
|
||||
process="@form" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
<p:commandButton
|
||||
value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
onclick="PF('resetPasswordDialog').hide()" />
|
||||
</div>
|
||||
</f:facet>
|
||||
<div class="field">
|
||||
<p:outputLabel for="newPass" value="Nouveau mot de passe" />
|
||||
<p:password id="newPass" value="#{targetBean.newPassword}" feedback="true" required="true"
|
||||
toggleMask="true" styleClass="w-full" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<p:outputLabel for="confPass" value="Confirmer" />
|
||||
<p:password id="confPass" value="#{targetBean.newPasswordConfirm}" required="true" toggleMask="true"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
<div class="flex justify-content-end gap-2">
|
||||
<p:commandButton value="Valider" icon="pi pi-check" action="#{actReset}" update="#{update}"
|
||||
oncomplete="PF('resetPasswordDialog_#{user.id}').hide()" />
|
||||
<p:commandButton value="Annuler" icon="pi pi-times" styleClass="p-button-secondary p-button-outlined"
|
||||
onclick="PF('resetPasswordDialog_#{user.id}').hide()" />
|
||||
</div>
|
||||
</h:form>
|
||||
</p:dialog>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,23 +1,25 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Carte Utilisateur (WOU/DRY Pattern)
|
||||
Composant réutilisable: Carte Utilisateur - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Affiche une carte utilisateur avec informations principales et actions
|
||||
Version: 2.1.0
|
||||
Description: Affiche une carte utilisateur avec avatar, informations principales,
|
||||
rôles et actions. Utilisable en mode carte standalone ou en grille.
|
||||
|
||||
Paramètres:
|
||||
- user: UserDTO (requis) - L'utilisateur à afficher
|
||||
- showActions: Boolean (défaut: true) - Afficher les boutons d'action
|
||||
- showRoles: Boolean (défaut: true) - Afficher les rôles de l'utilisateur
|
||||
- clickable: Boolean (défaut: true) - Rendre la carte cliquable
|
||||
- showOrganisation: Boolean (défaut: false) - Afficher l'organisation
|
||||
- clickable: Boolean (défaut: true) - Rendre la carte cliquable (bouton Voir)
|
||||
- outcome: String (optionnel) - Page de destination au clic
|
||||
- editOutcome: String (optionnel) - Page d'édition
|
||||
- deleteAction: MethodExpression (optionnel) - Action de suppression
|
||||
- hasDeleteAction: Boolean (défaut: false) - Si une action delete est fournie
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
|
||||
Exemples d'utilisation:
|
||||
@@ -27,104 +29,127 @@
|
||||
<ui:param name="user" value="#{userBean.selectedUser}" />
|
||||
</ui:include>
|
||||
|
||||
2. Carte avec actions:
|
||||
<ui:include src="/templates/components/user-management/user-card.xhtml">
|
||||
<ui:param name="user" value="#{userBean.selectedUser}" />
|
||||
<ui:param name="showActions" value="true" />
|
||||
<ui:param name="outcome" value="/pages/user-manager/users/profile" />
|
||||
</ui:include>
|
||||
|
||||
3. Carte sans rôles:
|
||||
<ui:include src="/templates/components/user-management/user-card.xhtml">
|
||||
<ui:param name="user" value="#{userBean.selectedUser}" />
|
||||
<ui:param name="showRoles" value="false" />
|
||||
</ui:include>
|
||||
2. Carte dans une grille:
|
||||
<c:forEach var="u" items="#{userBean.users}">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<ui:include src="/templates/components/user-management/user-card.xhtml">
|
||||
<ui:param name="user" value="#{u}" />
|
||||
<ui:param name="showOrganisation" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</c:forEach>
|
||||
-->
|
||||
|
||||
|
||||
<c:set var="showActions" value="#{empty showActions ? true : showActions}" />
|
||||
<c:set var="showRoles" value="#{empty showRoles ? true : showRoles}" />
|
||||
<c:set var="showOrganisation" value="#{empty showOrganisation ? false : showOrganisation}" />
|
||||
<c:set var="clickable" value="#{empty clickable ? true : clickable}" />
|
||||
|
||||
<p:card styleClass="user-card #{styleClass}" rendered="#{not empty user}">
|
||||
<c:set var="hasDeleteAction" value="#{empty hasDeleteAction ? false : hasDeleteAction}" />
|
||||
|
||||
<c:set var="userInitials"
|
||||
value="#{user.prenom != null and user.prenom.length() > 0 ? user.prenom.substring(0,1) : 'U'}#{user.nom != null and user.nom.length() > 0 ? user.nom.substring(0,1) : ''}" />
|
||||
|
||||
<p:card styleClass="user-card shadow-1 hover:shadow-3 transition-all transition-duration-200 #{styleClass}"
|
||||
rendered="#{not empty user}">
|
||||
<f:facet name="header">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<p:avatar
|
||||
label="#{user.prenom != null ? user.prenom.substring(0,1) : 'U'}#{user.nom != null ? user.nom.substring(0,1) : ''}"
|
||||
styleClass="user-avatar"
|
||||
size="large" />
|
||||
<div class="flex flex-column">
|
||||
<h3 class="m-0">#{user.prenom} #{user.nom}</h3>
|
||||
<div class="flex align-items-center gap-3 p-3">
|
||||
<!-- Avatar -->
|
||||
<p:avatar label="#{userInitials}" size="large" shape="circle"
|
||||
style="background-color: var(--primary-color); color: var(--primary-color-text);" />
|
||||
|
||||
<!-- Nom et Username -->
|
||||
<div class="flex flex-column flex-1">
|
||||
<h3 class="m-0 text-900">#{user.prenom} #{user.nom}</h3>
|
||||
<span class="text-color-secondary text-sm">@#{user.username}</span>
|
||||
</div>
|
||||
|
||||
<!-- Badge de statut -->
|
||||
<p:tag value="#{user.enabled ? 'Actif' : 'Inactif'}" severity="#{user.enabled ? 'success' : 'danger'}"
|
||||
icon="#{user.enabled ? 'pi pi-check' : 'pi pi-times'}" />
|
||||
</div>
|
||||
</f:facet>
|
||||
|
||||
|
||||
<div class="user-card-content">
|
||||
<!-- Informations principales -->
|
||||
<!-- Informations de contact -->
|
||||
<div class="flex flex-column gap-2 mb-3">
|
||||
<!-- Email -->
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-envelope text-color-secondary"></i>
|
||||
<span>#{user.email}</span>
|
||||
<i class="pi pi-envelope text-color-secondary text-sm" style="width: 1rem;"></i>
|
||||
<span class="text-sm">#{user.email}</span>
|
||||
<c:if test="#{user.emailVerified}">
|
||||
<i class="pi pi-verified text-green-500 text-xs" title="Email vérifié"></i>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Téléphone -->
|
||||
<c:if test="#{not empty user.telephone}">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-phone text-color-secondary"></i>
|
||||
<span>#{user.telephone}</span>
|
||||
<i class="pi pi-phone text-color-secondary text-sm" style="width: 1rem;"></i>
|
||||
<span class="text-sm">#{user.telephone}</span>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<!-- Organisation -->
|
||||
<c:if test="#{showOrganisation and not empty user.organisation}">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-building text-color-secondary text-sm" style="width: 1rem;"></i>
|
||||
<span class="text-sm">#{user.organisation}</span>
|
||||
<c:if test="#{not empty user.fonction}">
|
||||
<span class="text-color-secondary text-xs">- #{user.fonction}</span>
|
||||
</c:if>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<!-- Statut -->
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-circle-fill text-color-secondary"></i>
|
||||
<p:tag
|
||||
value="#{user.statut != null ? user.statut : 'INCONNU'}"
|
||||
severity="#{user.enabled ? 'success' : 'danger'}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Rôles -->
|
||||
<c:if test="#{showRoles and not empty user.roles}">
|
||||
<div class="flex flex-column gap-2 mb-3">
|
||||
<h5 class="m-0">Rôles</h5>
|
||||
<c:if test="#{showRoles}">
|
||||
<div class="flex flex-column gap-2">
|
||||
<span class="text-600 font-medium text-xs text-uppercase">Rôles</span>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<c:forEach var="role" items="#{user.roles}">
|
||||
<p:tag value="#{role.name}" severity="info" />
|
||||
</c:forEach>
|
||||
<c:choose>
|
||||
<c:when test="#{not empty user.realmRoles and not user.realmRoles.isEmpty()}">
|
||||
<c:forEach var="role" items="#{user.realmRoles}" varStatus="status">
|
||||
<c:if test="#{status.index < 4}">
|
||||
<p:tag value="#{role}" severity="info" styleClass="text-xs" />
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
<c:if test="#{user.realmRoles.size() > 4}">
|
||||
<p:tag value="+#{user.realmRoles.size() - 4}" severity="secondary"
|
||||
styleClass="text-xs"
|
||||
title="#{user.realmRoles.size() - 4} rôle(s) supplémentaire(s)" />
|
||||
</c:if>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<span class="text-color-secondary text-xs font-italic">Aucun rôle attribué</span>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Actions -->
|
||||
<f:facet name="footer">
|
||||
<c:if test="#{showActions}">
|
||||
<div class="flex gap-2 justify-content-end">
|
||||
<p:commandButton
|
||||
icon="pi pi-eye"
|
||||
title="Voir le profil"
|
||||
styleClass="p-button-text p-button-sm"
|
||||
outcome="#{not empty outcome ? outcome : '/pages/user-manager/users/profile'}"
|
||||
rendered="#{clickable}">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
</p:commandButton>
|
||||
|
||||
<p:commandButton
|
||||
icon="pi pi-pencil"
|
||||
title="Modifier"
|
||||
styleClass="p-button-text p-button-sm p-button-warning"
|
||||
outcome="/pages/user-manager/users/edit">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
</p:commandButton>
|
||||
|
||||
<p:commandButton
|
||||
icon="pi pi-trash"
|
||||
title="Supprimer"
|
||||
styleClass="p-button-text p-button-sm p-button-danger"
|
||||
onclick="PF('confirmDeleteDialog').show()" />
|
||||
<ui:include src="/templates/components/user-management/user-actions.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="iconOnly" value="true" />
|
||||
<ui:param name="layout" value="horizontal" />
|
||||
<ui:param name="showView" value="#{clickable}" />
|
||||
<ui:param name="showEdit" value="true" />
|
||||
<ui:param name="showDelete" value="#{hasDeleteAction}" />
|
||||
<ui:param name="showResetPassword" value="true" />
|
||||
<ui:param name="showActivate" value="true" />
|
||||
<ui:param name="showDeactivate" value="true" />
|
||||
<ui:param name="outcome" value="#{outcome}" />
|
||||
<ui:param name="actionBean" value="#{actionBean}" />
|
||||
<ui:param name="editOutcome" value="#{editOutcome}" />
|
||||
<ui:param name="deleteAction" value="#{deleteAction}" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</c:if>
|
||||
</f:facet>
|
||||
</p:card>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,216 +1,283 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<p:panel header="#{mode == 'create' ? 'Nouvel Utilisateur' : 'Modifier Utilisateur'}"
|
||||
styleClass="w-full">
|
||||
<!--
|
||||
Composant réutilisable: Contenu du formulaire Utilisateur - Écosystème LionsDev
|
||||
|
||||
<!-- Informations de base -->
|
||||
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 2.0.0
|
||||
Description: Corps du formulaire utilisateur (création/édition) avec validation
|
||||
et messages d'erreur. Doit être contenu dans un h:form ou utilisé
|
||||
via user-form.xhtml qui gère automatiquement le wrapper <h:form>.
|
||||
|
||||
Paramètres hérités de user-form.xhtml:
|
||||
- user: UserDTO (requis)
|
||||
- mode: String ("create" | "edit")
|
||||
- showRealmSelector: Boolean
|
||||
- showPasswordFields: Boolean
|
||||
- readonly: Boolean
|
||||
- hasSubmitAction: Boolean
|
||||
- submitAction: MethodExpression
|
||||
- submitOutcome: String
|
||||
- update: String
|
||||
- cancelOutcome: String
|
||||
-->
|
||||
|
||||
<p:messages id="formMessages" showDetail="true" closable="true" styleClass="mb-3" />
|
||||
|
||||
<p:panel header="#{mode == 'create' ? 'Nouvel Utilisateur' : 'Modifier Utilisateur'}" styleClass="w-full">
|
||||
|
||||
<!-- Section: Identité -->
|
||||
<h4 class="text-primary mt-0 mb-3">
|
||||
<i class="pi pi-id-card mr-2"></i>Identité
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Username -->
|
||||
<p:outputLabel for="username" value="Nom d'utilisateur *" />
|
||||
<p:inputText id="username"
|
||||
value="#{user.username}"
|
||||
required="true"
|
||||
readonly="#{readonly or mode == 'edit'}"
|
||||
placeholder="jdupont"
|
||||
styleClass="w-full">
|
||||
<f:validateLength minimum="3" maximum="100" />
|
||||
<f:validateRegex pattern="^[a-zA-Z0-9._-]+$" />
|
||||
</p:inputText>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="username" value="Nom d'utilisateur" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:inputText id="username" value="#{user.username}" required="true"
|
||||
readonly="#{readonly or mode == 'edit'}" placeholder="jdupont" styleClass="w-full">
|
||||
<f:validateLength minimum="3" maximum="100" />
|
||||
<f:validateRegex pattern="^[a-zA-Z0-9._-]+$" />
|
||||
</p:inputText>
|
||||
<p:message for="username" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<p:outputLabel for="email" value="Email *" />
|
||||
<p:inputText id="email"
|
||||
value="#{user.email}"
|
||||
required="true"
|
||||
readonly="#{readonly}"
|
||||
placeholder="jean.dupont@lions.dev"
|
||||
styleClass="w-full">
|
||||
<f:validateLength minimum="5" maximum="255" />
|
||||
</p:inputText>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="email" value="Email" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:inputText id="email" value="#{user.email}" required="true" readonly="#{readonly}"
|
||||
placeholder="jean.dupont@lions.dev" type="email" styleClass="w-full">
|
||||
<f:validateLength minimum="5" maximum="255" />
|
||||
</p:inputText>
|
||||
<p:message for="email" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prénom -->
|
||||
<p:outputLabel for="prenom" value="Prénom *" />
|
||||
<p:inputText id="prenom"
|
||||
value="#{user.prenom}"
|
||||
required="true"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Jean"
|
||||
styleClass="w-full">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
</p:inputText>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="prenom" value="Prénom" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:inputText id="prenom" value="#{user.prenom}" required="true" readonly="#{readonly}"
|
||||
placeholder="Jean" styleClass="w-full">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
</p:inputText>
|
||||
<p:message for="prenom" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nom -->
|
||||
<p:outputLabel for="nom" value="Nom *" />
|
||||
<p:inputText id="nom"
|
||||
value="#{user.nom}"
|
||||
required="true"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Dupont"
|
||||
styleClass="w-full">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
</p:inputText>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="nom" value="Nom" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:inputText id="nom" value="#{user.nom}" required="true" readonly="#{readonly}"
|
||||
placeholder="Dupont" styleClass="w-full">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
</p:inputText>
|
||||
<p:message for="nom" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p:separator />
|
||||
|
||||
<!-- Section: Coordonnées -->
|
||||
<h4 class="text-primary mt-2 mb-3">
|
||||
<i class="pi pi-map-marker mr-2"></i>Coordonnées
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Téléphone -->
|
||||
<p:outputLabel for="telephone" value="Téléphone" />
|
||||
<p:inputText id="telephone"
|
||||
value="#{user.telephone}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="+225 07 12 34 56 78"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- Organisation -->
|
||||
<p:outputLabel for="organisation" value="Organisation" />
|
||||
<p:inputText id="organisation"
|
||||
value="#{user.organisation}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Lions Dev"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- Département -->
|
||||
<p:outputLabel for="departement" value="Département" />
|
||||
<p:inputText id="departement"
|
||||
value="#{user.departement}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="IT"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- Fonction -->
|
||||
<p:outputLabel for="fonction" value="Fonction" />
|
||||
<p:inputText id="fonction"
|
||||
value="#{user.fonction}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Développeur Senior"
|
||||
styleClass="w-full" />
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="telephone" value="Téléphone" styleClass="font-medium" />
|
||||
<p:inputText id="telephone" value="#{user.telephone}" readonly="#{readonly}"
|
||||
placeholder="+225 07 12 34 56 78" styleClass="w-full" />
|
||||
<p:message for="telephone" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ville -->
|
||||
<p:outputLabel for="ville" value="Ville" />
|
||||
<p:inputText id="ville"
|
||||
value="#{user.ville}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Abidjan"
|
||||
styleClass="w-full" />
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="ville" value="Ville" styleClass="font-medium" />
|
||||
<p:inputText id="ville" value="#{user.ville}" readonly="#{readonly}" placeholder="Abidjan"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pays -->
|
||||
<p:outputLabel for="pays" value="Pays" />
|
||||
<p:inputText id="pays"
|
||||
value="#{user.pays}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Côte d'Ivoire"
|
||||
styleClass="w-full" />
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="pays" value="Pays" styleClass="font-medium" />
|
||||
<p:inputText id="pays" value="#{user.pays}" readonly="#{readonly}" placeholder="Côte d'Ivoire"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p:separator />
|
||||
|
||||
<!-- Section: Organisation -->
|
||||
<h4 class="text-primary mt-2 mb-3">
|
||||
<i class="pi pi-building mr-2"></i>Organisation
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Organisation -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="organisation" value="Organisation" styleClass="font-medium" />
|
||||
<p:inputText id="organisation" value="#{user.organisation}" readonly="#{readonly}"
|
||||
placeholder="Lions Dev" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Département -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="departement" value="Département" styleClass="font-medium" />
|
||||
<p:inputText id="departement" value="#{user.departement}" readonly="#{readonly}" placeholder="IT"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fonction -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="fonction" value="Fonction" styleClass="font-medium" />
|
||||
<p:inputText id="fonction" value="#{user.fonction}" readonly="#{readonly}"
|
||||
placeholder="Développeur Senior" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p:separator />
|
||||
|
||||
<!-- Section: Statut et Paramètres -->
|
||||
<h4 class="text-primary mt-2 mb-3">
|
||||
<i class="pi pi-cog mr-2"></i>Statut et Paramètres
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Statut -->
|
||||
<p:outputLabel for="statut" value="Statut" />
|
||||
<p:selectOneMenu id="statut"
|
||||
value="#{user.statut}"
|
||||
readonly="#{readonly}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<f:selectItems value="#{userBean.statutOptions}" />
|
||||
</p:selectOneMenu>
|
||||
|
||||
<!-- Enabled -->
|
||||
<p:outputLabel for="enabled" value="Compte activé" />
|
||||
<p:selectBooleanCheckbox id="enabled"
|
||||
value="#{user.enabled}"
|
||||
readonly="#{readonly}" />
|
||||
|
||||
<!-- Email vérifié -->
|
||||
<p:outputLabel for="emailVerified" value="Email vérifié" />
|
||||
<p:selectBooleanCheckbox id="emailVerified"
|
||||
value="#{user.emailVerified}"
|
||||
readonly="#{readonly}" />
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="statut" value="Statut" styleClass="font-medium" />
|
||||
<p:selectOneMenu id="statut" value="#{user.statut}" readonly="#{readonly}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{userBean.statutOptions}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Realm (si affiché) -->
|
||||
<c:if test="#{showRealmSelector}">
|
||||
<p:outputLabel for="realmName" value="Realm *" />
|
||||
<p:selectOneMenu id="realmName"
|
||||
value="#{user.realmName}"
|
||||
required="#{showRealmSelector}"
|
||||
readonly="#{readonly}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" />
|
||||
<f:selectItems value="#{userBean.availableRealms}" />
|
||||
</p:selectOneMenu>
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="realmName" value="Realm" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:selectOneMenu id="realmName" value="#{user.realmName}" required="#{showRealmSelector}"
|
||||
readonly="#{readonly}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner..." itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{userBean.availableRealms}" />
|
||||
</p:selectOneMenu>
|
||||
<p:message for="realmName" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
</p:panelGrid>
|
||||
|
||||
<!-- Champs mot de passe (si affichés) -->
|
||||
|
||||
<!-- Enabled -->
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="field">
|
||||
<div class="flex align-items-center gap-2 mt-4">
|
||||
<p:selectBooleanCheckbox id="enabled" value="#{user.enabled}" readonly="#{readonly}" />
|
||||
<p:outputLabel for="enabled" value="Compte activé" styleClass="font-medium" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email vérifié -->
|
||||
<div class="col-12 md:col-3">
|
||||
<div class="field">
|
||||
<div class="flex align-items-center gap-2 mt-4">
|
||||
<p:selectBooleanCheckbox id="emailVerified" value="#{user.emailVerified}"
|
||||
readonly="#{readonly}" />
|
||||
<p:outputLabel for="emailVerified" value="Email vérifié" styleClass="font-medium" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Champs mot de passe (mode création uniquement) -->
|
||||
<c:if test="#{showPasswordFields and mode == 'create'}">
|
||||
<p:separator />
|
||||
<h3>Mot de passe</h3>
|
||||
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
|
||||
<p:outputLabel for="password" value="Mot de passe *" />
|
||||
<p:password id="password"
|
||||
value="#{userBean.password}"
|
||||
required="true"
|
||||
feedback="true"
|
||||
placeholder="Minimum 8 caractères"
|
||||
styleClass="w-full">
|
||||
<f:validateLength minimum="8" maximum="100" />
|
||||
</p:password>
|
||||
|
||||
<p:outputLabel for="passwordConfirm" value="Confirmer le mot de passe *" />
|
||||
<p:password id="passwordConfirm"
|
||||
value="#{userBean.passwordConfirm}"
|
||||
required="true"
|
||||
placeholder="Répétez le mot de passe"
|
||||
styleClass="w-full" />
|
||||
</p:panelGrid>
|
||||
|
||||
<h4 class="text-primary mt-2 mb-3">
|
||||
<i class="pi pi-lock mr-2"></i>Mot de passe
|
||||
</h4>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="password" value="Mot de passe" styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:password id="password" value="#{userBean.password}" required="true" feedback="true"
|
||||
toggleMask="true" placeholder="Minimum 8 caractères" styleClass="w-full">
|
||||
<f:validateLength minimum="8" maximum="100" />
|
||||
</p:password>
|
||||
<p:message for="password" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="passwordConfirm" value="Confirmer le mot de passe"
|
||||
styleClass="font-medium" />
|
||||
<span class="text-red-500 ml-1">*</span>
|
||||
<p:password id="passwordConfirm" value="#{userBean.passwordConfirm}" required="true"
|
||||
toggleMask="true" placeholder="Répétez le mot de passe" styleClass="w-full" />
|
||||
<p:message for="passwordConfirm" display="text" styleClass="mt-1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
|
||||
<!-- Boutons d'action -->
|
||||
<f:facet name="footer">
|
||||
<div class="flex gap-2 justify-content-end">
|
||||
<c:if test="#{not readonly}">
|
||||
<c:choose>
|
||||
<!-- Si hasSubmitAction est explicitement défini à true, utiliser action -->
|
||||
<c:when test="#{hasSubmitAction == true}">
|
||||
<p:commandButton
|
||||
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success"
|
||||
action="#{submitAction}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="@form" />
|
||||
<p:commandButton value="#{mode == 'create' ? 'Créer' : 'Enregistrer'}" icon="pi pi-check"
|
||||
styleClass="p-button-success" action="#{submitAction}"
|
||||
update="#{not empty update ? update : '@form'}" process="@form" validateClient="true" />
|
||||
</c:when>
|
||||
<!-- Si submitOutcome est fourni, utiliser outcome -->
|
||||
<c:when test="#{not empty submitOutcome}">
|
||||
<p:commandButton
|
||||
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success"
|
||||
outcome="#{submitOutcome}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="@form" />
|
||||
<p:commandButton value="#{mode == 'create' ? 'Créer' : 'Enregistrer'}" icon="pi pi-check"
|
||||
styleClass="p-button-success" outcome="#{submitOutcome}" />
|
||||
</c:when>
|
||||
<!-- Sinon, essayer d'utiliser submitAction si fourni -->
|
||||
<c:otherwise>
|
||||
<p:commandButton
|
||||
value="#{mode == 'create' ? 'Créer' : 'Modifier'}"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success"
|
||||
action="#{submitAction}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="@form" />
|
||||
<p:commandButton value="#{mode == 'create' ? 'Créer' : 'Enregistrer'}" icon="pi pi-check"
|
||||
styleClass="p-button-success" action="#{submitAction}"
|
||||
update="#{not empty update ? update : '@form'}" process="@form" validateClient="true" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if>
|
||||
<p:commandButton
|
||||
value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
<p:commandButton value="Annuler" icon="pi pi-times" styleClass="p-button-secondary p-button-outlined"
|
||||
outcome="#{not empty cancelOutcome ? cancelOutcome : '/pages/user-manager/users/list'}"
|
||||
immediate="true" />
|
||||
</div>
|
||||
</f:facet>
|
||||
</p:panel>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,105 +1,55 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Badge de Rôle Utilisateur (WOU/DRY Pattern)
|
||||
Composant réutilisable: Badge de Rôle Utilisateur - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 1.0.0
|
||||
Description: Affiche un badge pour un rôle utilisateur avec icône et couleur
|
||||
|
||||
Paramètres:
|
||||
- roleName: String (requis) - Nom du rôle
|
||||
- roleType: String (optionnel) - Type de rôle: "REALM_ROLE", "CLIENT_ROLE", "COMPOSITE_ROLE"
|
||||
- severity: String (optionnel) - Severity PrimeFaces: "success", "info", "warning", "danger" (défaut: "info")
|
||||
- showIcon: Boolean (défaut: true) - Afficher l'icône
|
||||
- icon: String (optionnel) - Classe d'icône PrimeIcons (défaut: "pi-shield")
|
||||
- size: String (optionnel) - Taille: "small", "normal", "large" (défaut: "normal")
|
||||
- clickable: Boolean (défaut: false) - Rendre le badge cliquable
|
||||
- clickAction: String (optionnel) - Action au clic
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
1. Badge simple:
|
||||
<ui:include src="/templates/components/user-management/user-role-badge.xhtml">
|
||||
<ui:param name="roleName" value="ADMIN" />
|
||||
</ui:include>
|
||||
|
||||
2. Badge avec type:
|
||||
<ui:include src="/templates/components/user-management/user-role-badge.xhtml">
|
||||
<ui:param name="roleName" value="USER" />
|
||||
<ui:param name="roleType" value="REALM_ROLE" />
|
||||
<ui:param name="severity" value="success" />
|
||||
</ui:include>
|
||||
|
||||
3. Badge cliquable:
|
||||
<ui:include src="/templates/components/user-management/user-role-badge.xhtml">
|
||||
<ui:param name="roleName" value="MODERATOR" />
|
||||
<ui:param name="clickable" value="true" />
|
||||
<ui:param name="clickAction" value="#{roleBean.viewRole(roleName)}" />
|
||||
</ui:include>
|
||||
Version: 2.0.0
|
||||
Description: Badge typé pour afficher un rôle avec icône et couleur sémantique.
|
||||
-->
|
||||
|
||||
|
||||
<c:set var="showIcon" value="#{empty showIcon ? true : showIcon}" />
|
||||
<c:set var="size" value="#{empty size ? 'normal' : size}" />
|
||||
<c:set var="clickable" value="#{empty clickable ? false : clickable}" />
|
||||
<c:set var="severity" value="#{empty severity ? 'info' : severity}" />
|
||||
<c:set var="icon" value="#{empty icon ? 'pi-shield' : icon}" />
|
||||
|
||||
<!-- Déterminer la severity selon le type de rôle -->
|
||||
|
||||
<!-- Logique de couleur/icône centralisée -->
|
||||
<c:choose>
|
||||
<c:when test="#{roleType == 'REALM_ROLE'}">
|
||||
<c:set var="severity" value="success" />
|
||||
<c:set var="icon" value="pi-check-circle" />
|
||||
</c:when>
|
||||
<c:when test="#{roleType == 'CLIENT_ROLE'}">
|
||||
<c:set var="severity" value="info" />
|
||||
<c:set var="icon" value="pi-desktop" />
|
||||
</c:when>
|
||||
<c:when test="#{roleType == 'COMPOSITE_ROLE'}">
|
||||
<c:set var="severity" value="warning" />
|
||||
</c:when>
|
||||
</c:choose>
|
||||
|
||||
<!-- Déterminer la taille -->
|
||||
<c:choose>
|
||||
<c:when test="#{size == 'small'}">
|
||||
<c:set var="tagStyleClass" value="text-xs" />
|
||||
</c:when>
|
||||
<c:when test="#{size == 'large'}">
|
||||
<c:set var="tagStyleClass" value="text-base" />
|
||||
<c:set var="icon" value="pi-sitemap" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<c:set var="tagStyleClass" value="text-sm" />
|
||||
<c:set var="severity" value="#{empty severity ? 'info' : severity}" />
|
||||
<c:set var="icon" value="#{empty icon ? 'pi-shield' : icon}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
<c:choose>
|
||||
<!-- Badge cliquable -->
|
||||
<c:when test="#{clickable and not empty clickAction}">
|
||||
<p:commandLink
|
||||
styleClass="role-badge-link"
|
||||
action="#{clickAction}">
|
||||
<p:tag
|
||||
value="#{roleName}"
|
||||
severity="#{severity}"
|
||||
icon="#{showIcon ? icon : ''}"
|
||||
styleClass="#{tagStyleClass} #{styleClass}" />
|
||||
</p:commandLink>
|
||||
</c:when>
|
||||
|
||||
<!-- Badge simple -->
|
||||
<c:otherwise>
|
||||
<p:tag
|
||||
value="#{roleName}"
|
||||
severity="#{severity}"
|
||||
icon="#{showIcon ? icon : ''}"
|
||||
styleClass="#{tagStyleClass} #{styleClass}" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
<!-- Override manuel de l'icône si fourni -->
|
||||
<c:if test="#{not empty paramIcon}">
|
||||
<c:set var="icon" value="#{paramIcon}" />
|
||||
</c:if>
|
||||
|
||||
<c:set var="tagClass"
|
||||
value="#{size == 'small' ? 'text-xs' : (size == 'large' ? 'text-lg' : 'text-sm')} #{styleClass}" />
|
||||
|
||||
<p:tag value="#{roleName}" severity="#{severity}" icon="#{showIcon ? 'pi '.concat(icon) : ''}"
|
||||
styleClass="#{tagClass}">
|
||||
|
||||
<c:if test="#{clickable}">
|
||||
<p:commandLink action="#{clickAction}" styleClass="ml-2">
|
||||
<i class="pi pi-external-link text-xs text-white"></i>
|
||||
</p:commandLink>
|
||||
</c:if>
|
||||
</p:tag>
|
||||
|
||||
</ui:composition>
|
||||
@@ -0,0 +1,81 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Gestion des rôles utilisateur
|
||||
|
||||
Paramètres:
|
||||
- user: UserDTO (requis)
|
||||
- realm: String (requis)
|
||||
- roleBean: RoleGestionBean (requis)
|
||||
-->
|
||||
|
||||
<c:set var="userRoles" value="#{user.realmRoles}" />
|
||||
<c:set var="availableRoles" value="#{roleBean.realmRoles}" />
|
||||
|
||||
<p:panel header="Gestion des Rôles" styleClass="w-full">
|
||||
<p:messages id="roleMessages" showDetail="true" closable="true" styleClass="mb-3" />
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<!-- Rôles assignés -->
|
||||
<h4 class="text-primary mt-0 mb-3">
|
||||
<i class="pi pi-check-circle mr-2"></i>Rôles Assignés
|
||||
</h4>
|
||||
|
||||
<p:dataTable value="#{userRoles}" var="roleName" emptyMessage="Aucun rôle assigné"
|
||||
styleClass="border-1 surface-border border-round" size="small">
|
||||
<p:column headerText="Nom du rôle">
|
||||
<span class="font-medium">#{roleName}</span>
|
||||
</p:column>
|
||||
<p:column style="width: 4rem; text-align: center">
|
||||
<p:commandButton icon="pi pi-trash" styleClass="p-button-rounded p-button-danger p-button-text"
|
||||
action="#{roleBean.revokeRoleFromUser(user.id, roleName)}" process="@this" update="@form"
|
||||
title="Révoquer ce rôle">
|
||||
<p:confirm header="Confirmation" message="Révoquer le rôle '#{roleName}' ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<!-- Ajouter un rôle -->
|
||||
<h4 class="text-primary mt-0 mb-3">
|
||||
<i class="pi pi-plus-circle mr-2"></i>Ajouter un Rôle
|
||||
</h4>
|
||||
|
||||
<div class="card bg-bluegray-50 border-none p-4">
|
||||
<div class="field">
|
||||
<p:outputLabel for="rolesPicklist" value="Rôles disponibles"
|
||||
styleClass="font-medium mb-2 block" />
|
||||
<p:selectOneMenu id="rolesPicklist" value="#{roleBean.selectedRoleName}" filter="true"
|
||||
filterMatchMode="contains" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner un rôle..." itemValue="#{null}"
|
||||
noSelectionOption="true" />
|
||||
<f:selectItems value="#{availableRoles}" var="role" itemLabel="#{role.name}"
|
||||
itemValue="#{role.name}" itemDisabled="#{userRoles.contains(role.name)}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<p:commandButton value="Attribuer le rôle" icon="pi pi-check"
|
||||
actionListener="#{roleBean.assignRoleToUser(user.id, roleBean.selectedRoleName)}" update="@form"
|
||||
process="@parent" styleClass="w-full mt-2" />
|
||||
</div>
|
||||
|
||||
<div class="mt-4 surface-card p-3 border-round shadow-1">
|
||||
<div class="flex align-items-center mb-2">
|
||||
<i class="pi pi-info-circle text-blue-500 mr-2 text-xl"></i>
|
||||
<span class="font-bold text-900">Information</span>
|
||||
</div>
|
||||
<p class="text-sm text-700 m-0 line-height-3">
|
||||
Les rôles définissent les permissions de l'utilisateur dans l'application.
|
||||
Certains rôles peuvent donner accès à des fonctionnalités administratives sensibles.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,181 +1,184 @@
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
<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:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Contenu Barre de Recherche Utilisateur - Écosystème LionsDev
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 2.0.0
|
||||
Description: Barre de recherche avec filtres rapides et options avancées.
|
||||
Utilisé dans user-search-bar.xhtml, ne pas inclure directement.
|
||||
|
||||
Paramètres hérités de user-search-bar.xhtml:
|
||||
- searchCriteria, searchAction, update
|
||||
- showRealmFilter, showStatusFilter, showRoleFilter, showAdvanced
|
||||
-->
|
||||
|
||||
<p:panel styleClass="w-full mb-3">
|
||||
<f:facet name="header">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span>Recherche d'utilisateurs</span>
|
||||
<p:commandButton
|
||||
icon="pi pi-filter"
|
||||
styleClass="p-button-text p-button-sm"
|
||||
onclick="PF('advancedSearchDialog').toggle()"
|
||||
rendered="#{showAdvanced}"
|
||||
title="Options avancées" />
|
||||
<span class="flex align-items-center gap-2">
|
||||
<i class="pi pi-search"></i>
|
||||
Recherche d'utilisateurs
|
||||
</span>
|
||||
<c:if test="#{showAdvanced}">
|
||||
<p:commandButton icon="pi pi-filter" styleClass="p-button-text p-button-sm p-button-rounded"
|
||||
onclick="PF('advancedSearchDialog').toggle()" title="Options avancées" type="button" />
|
||||
</c:if>
|
||||
</div>
|
||||
</f:facet>
|
||||
|
||||
|
||||
<!-- Recherche rapide -->
|
||||
<div class="flex gap-2 align-items-end">
|
||||
<div class="flex-1">
|
||||
<p:outputLabel for="searchText" value="Rechercher" />
|
||||
<p:inputText id="searchText"
|
||||
value="#{searchCriteria.searchTerm}"
|
||||
placeholder="Nom, prénom, email, username..."
|
||||
styleClass="w-full">
|
||||
<p:ajax event="keyup"
|
||||
delay="500"
|
||||
listener="#{searchAction}"
|
||||
update="#{update}" />
|
||||
</p:inputText>
|
||||
<div class="flex gap-2 align-items-end flex-wrap">
|
||||
<div class="flex-1" style="min-width: 250px;">
|
||||
<p:outputLabel for="searchText" value="Rechercher" styleClass="font-medium" />
|
||||
<div class="p-inputgroup mt-1">
|
||||
<span class="p-inputgroup-addon">
|
||||
<i class="pi pi-search"></i>
|
||||
</span>
|
||||
<p:inputText id="searchText" value="#{searchCriteria.searchTerm}"
|
||||
placeholder="Nom, prénom, email, username..." styleClass="w-full">
|
||||
<p:ajax event="keyup" delay="500" listener="#{searchAction}" update="#{update}" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Filtre Realm -->
|
||||
<c:if test="#{showRealmFilter}">
|
||||
<div style="width: 200px;">
|
||||
<p:outputLabel for="realmFilter" value="Realm" />
|
||||
<p:selectOneMenu id="realmFilter"
|
||||
value="#{searchCriteria.realmName}"
|
||||
styleClass="w-full">
|
||||
<div style="min-width: 180px;">
|
||||
<p:outputLabel for="realmFilter" value="Realm" styleClass="font-medium" />
|
||||
<p:selectOneMenu id="realmFilter" value="#{searchCriteria.realmName}" styleClass="w-full mt-1">
|
||||
<f:selectItem itemLabel="Tous les realms" itemValue="" />
|
||||
<f:selectItems value="#{userBean.availableRealms}" />
|
||||
<p:ajax event="change" listener="#{searchAction}" update="#{update}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
|
||||
<!-- Filtre Statut -->
|
||||
<c:if test="#{showStatusFilter}">
|
||||
<div style="width: 180px;">
|
||||
<p:outputLabel for="statusFilter" value="Statut" />
|
||||
<p:selectOneMenu id="statusFilter"
|
||||
value="#{searchCriteria.statut}"
|
||||
styleClass="w-full">
|
||||
<div style="min-width: 150px;">
|
||||
<p:outputLabel for="statusFilter" value="Statut" styleClass="font-medium" />
|
||||
<p:selectOneMenu id="statusFilter" value="#{searchCriteria.statut}" styleClass="w-full mt-1">
|
||||
<f:selectItem itemLabel="Tous les statuts" itemValue="" />
|
||||
<f:selectItems value="#{userBean.statutOptions}" />
|
||||
<p:ajax event="change" listener="#{searchAction}" update="#{update}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<!-- Bouton Rechercher -->
|
||||
<div>
|
||||
<p:commandButton
|
||||
value="Rechercher"
|
||||
icon="pi pi-search"
|
||||
styleClass="p-button-primary"
|
||||
action="#{searchAction}"
|
||||
update="#{update}"
|
||||
process="@form" />
|
||||
</div>
|
||||
|
||||
<!-- Bouton Réinitialiser -->
|
||||
<div>
|
||||
<p:commandButton
|
||||
value="Réinitialiser"
|
||||
icon="pi pi-refresh"
|
||||
styleClass="p-button-secondary"
|
||||
action="#{userBean.resetSearch}"
|
||||
update="#{update}"
|
||||
process="@form" />
|
||||
|
||||
<!-- Boutons -->
|
||||
<div class="flex gap-2 align-items-end">
|
||||
<p:commandButton value="Rechercher" icon="pi pi-search" styleClass="p-button-primary"
|
||||
action="#{searchAction}" update="#{update}" process="@form" />
|
||||
|
||||
<p:commandButton icon="pi pi-refresh" title="Réinitialiser"
|
||||
styleClass="p-button-secondary p-button-outlined p-button-rounded" action="#{userBean.resetSearch}"
|
||||
update="#{update}" process="@form" />
|
||||
</div>
|
||||
</div>
|
||||
</p:panel>
|
||||
|
||||
|
||||
<!-- Options avancées (Dialog) -->
|
||||
<c:if test="#{showAdvanced}">
|
||||
<p:dialog id="advancedSearchDialog"
|
||||
header="Options de recherche avancées"
|
||||
widgetVar="advancedSearchDialog"
|
||||
modal="true"
|
||||
resizable="false"
|
||||
styleClass="w-full md:w-6">
|
||||
|
||||
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
|
||||
<p:dialog id="advancedSearchDialog" header="Options de recherche avancées" widgetVar="advancedSearchDialog"
|
||||
modal="true" resizable="false" draggable="true" responsive="true" styleClass="w-11 md:w-6"
|
||||
closeOnEscape="true">
|
||||
|
||||
<div class="grid">
|
||||
<!-- Email -->
|
||||
<p:outputLabel for="emailFilter" value="Email" />
|
||||
<p:inputText id="emailFilter"
|
||||
value="#{searchCriteria.email}"
|
||||
placeholder="email@example.com"
|
||||
styleClass="w-full" />
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="emailFilter" value="Email" styleClass="font-medium" />
|
||||
<p:inputText id="emailFilter" value="#{searchCriteria.email}" placeholder="email@example.com"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Téléphone -->
|
||||
<p:outputLabel for="phoneFilter" value="Téléphone" />
|
||||
<p:inputText id="phoneFilter"
|
||||
value="#{searchCriteria.telephone}"
|
||||
placeholder="+225 07 12 34 56 78"
|
||||
styleClass="w-full" />
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="phoneFilter" value="Téléphone" styleClass="font-medium" />
|
||||
<p:inputText id="phoneFilter" value="#{searchCriteria.telephone}"
|
||||
placeholder="+225 07 12 34 56 78" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Organisation -->
|
||||
<p:outputLabel for="orgFilter" value="Organisation" />
|
||||
<p:inputText id="orgFilter"
|
||||
value="#{searchCriteria.organisation}"
|
||||
placeholder="Nom de l'organisation"
|
||||
styleClass="w-full" />
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="orgFilter" value="Organisation" styleClass="font-medium" />
|
||||
<p:inputText id="orgFilter" value="#{searchCriteria.organisation}"
|
||||
placeholder="Nom de l'organisation" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ville -->
|
||||
<p:outputLabel for="cityFilter" value="Ville" />
|
||||
<p:inputText id="cityFilter"
|
||||
value="#{searchCriteria.ville}"
|
||||
placeholder="Nom de la ville"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- Rôle (si affiché) -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="cityFilter" value="Ville" styleClass="font-medium" />
|
||||
<p:inputText id="cityFilter" value="#{searchCriteria.ville}" placeholder="Nom de la ville"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Rôle -->
|
||||
<c:if test="#{showRoleFilter}">
|
||||
<p:outputLabel for="roleFilter" value="Rôle Realm" />
|
||||
<p:selectManyMenu id="roleFilter"
|
||||
value="#{searchCriteria.realmRoles}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les rôles" itemValue="" />
|
||||
<f:selectItems value="#{userBean.availableRoles}" />
|
||||
</p:selectManyMenu>
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="roleFilter" value="Rôle Realm" styleClass="font-medium" />
|
||||
<p:selectCheckboxMenu id="roleFilter" value="#{searchCriteria.realmRoles}"
|
||||
label="Sélectionner des rôles" multiple="true" filter="true" filterMatchMode="contains"
|
||||
styleClass="w-full">
|
||||
<f:selectItems value="#{userBean.availableRoles}" />
|
||||
</p:selectCheckboxMenu>
|
||||
</div>
|
||||
</div>
|
||||
</c:if>
|
||||
|
||||
<!-- Date de création (début) -->
|
||||
<p:outputLabel for="dateDebut" value="Date de création (début)" />
|
||||
<p:calendar id="dateDebut"
|
||||
value="#{searchCriteria.dateCreationMin}"
|
||||
pattern="dd/MM/yyyy"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- Date de création (fin) -->
|
||||
<p:outputLabel for="dateFin" value="Date de création (fin)" />
|
||||
<p:calendar id="dateFin"
|
||||
value="#{searchCriteria.dateCreationMax}"
|
||||
pattern="dd/MM/yyyy"
|
||||
styleClass="w-full" />
|
||||
|
||||
|
||||
<!-- Enabled -->
|
||||
<p:outputLabel for="enabledFilter" value="Compte activé" />
|
||||
<p:selectOneMenu id="enabledFilter"
|
||||
value="#{searchCriteria.enabled}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous" itemValue="" />
|
||||
<f:selectItem itemLabel="Activé" itemValue="true" />
|
||||
<f:selectItem itemLabel="Désactivé" itemValue="false" />
|
||||
</p:selectOneMenu>
|
||||
</p:panelGrid>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="enabledFilter" value="Compte activé" styleClass="font-medium" />
|
||||
<p:selectOneMenu id="enabledFilter" value="#{searchCriteria.enabled}" styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous" itemValue="" />
|
||||
<f:selectItem itemLabel="Activé" itemValue="true" />
|
||||
<f:selectItem itemLabel="Désactivé" itemValue="false" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date de création (début) -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="dateDebut" value="Date de création (début)" styleClass="font-medium" />
|
||||
<p:datePicker id="dateDebut" value="#{searchCriteria.dateCreationMin}" pattern="dd/MM/yyyy"
|
||||
showIcon="true" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date de création (fin) -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<p:outputLabel for="dateFin" value="Date de création (fin)" styleClass="font-medium" />
|
||||
<p:datePicker id="dateFin" value="#{searchCriteria.dateCreationMax}" pattern="dd/MM/yyyy"
|
||||
showIcon="true" styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<f:facet name="footer">
|
||||
<div class="flex gap-2 justify-content-end">
|
||||
<p:commandButton
|
||||
value="Appliquer"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-primary"
|
||||
action="#{searchAction}"
|
||||
update="#{update}"
|
||||
onclick="PF('advancedSearchDialog').hide()"
|
||||
<p:commandButton value="Appliquer les filtres" icon="pi pi-check" styleClass="p-button-primary"
|
||||
action="#{searchAction}" update="#{update}" oncomplete="PF('advancedSearchDialog').hide()"
|
||||
process="@form" />
|
||||
<p:commandButton
|
||||
value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
onclick="PF('advancedSearchDialog').hide()" />
|
||||
<p:commandButton value="Annuler" icon="pi pi-times"
|
||||
styleClass="p-button-secondary p-button-outlined" onclick="PF('advancedSearchDialog').hide()"
|
||||
type="button" />
|
||||
</div>
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
</c:if>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
</ui:composition>
|
||||
@@ -1,55 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html 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"
|
||||
lang="fr">
|
||||
<html 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" lang="fr">
|
||||
|
||||
<h:head>
|
||||
<f:facet name="first">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="icon" href="#{request.contextPath}/resources/freya-layout/images/favicon.ico" type="image/x-icon"></link>
|
||||
</f:facet>
|
||||
<title><ui:insert name="title">Lions User Manager</ui:insert></title>
|
||||
<h:outputScript name="js/layout.js" library="freya-layout" />
|
||||
<h:outputScript name="js/prism.js" library="freya-layout"/>
|
||||
<ui:insert name="head"/>
|
||||
</h:head>
|
||||
<h:head>
|
||||
<f:facet name="first">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="icon" href="#{request.contextPath}/resources/freya-layout/images/favicon.ico" type="image/x-icon">
|
||||
</link>
|
||||
</f:facet>
|
||||
<title>
|
||||
<ui:insert name="title">Lions User Manager</ui:insert>
|
||||
</title>
|
||||
<h:outputScript name="js/layout.js" library="freya-layout" />
|
||||
<h:outputScript name="js/prism.js" library="freya-layout" />
|
||||
<ui:insert name="head" />
|
||||
</h:head>
|
||||
|
||||
<h:body styleClass="#{guestPreferences.inputStyleClass}">
|
||||
<div class="layout-wrapper layout-topbar-#{guestPreferences.topbarTheme} layout-menu-#{guestPreferences.menuTheme} #{guestPreferences.menuMode}" >
|
||||
<ui:include src="/templates/components/layout/topbar.xhtml"/>
|
||||
|
||||
<div class="layout-main">
|
||||
<div class="layout-content">
|
||||
<p:messages id="messages" showDetail="true" closable="true" />
|
||||
<ui:insert name="content"/>
|
||||
</div>
|
||||
<ui:include src="/templates/components/layout/footer.xhtml"/>
|
||||
<h:body styleClass="#{guestPreferences.inputStyleClass}">
|
||||
<div
|
||||
class="layout-wrapper layout-topbar-#{guestPreferences.topbarTheme} layout-menu-#{guestPreferences.menuTheme} #{guestPreferences.menuMode}">
|
||||
<ui:include src="/templates/components/layout/topbar.xhtml" />
|
||||
|
||||
<div class="layout-main">
|
||||
<div class="layout-content">
|
||||
<p:messages id="messages" showDetail="true" closable="true" />
|
||||
<ui:insert name="content" />
|
||||
</div>
|
||||
|
||||
<p:ajaxStatus style="width:32px;height:32px;position:fixed;right:7px;bottom:7px">
|
||||
<f:facet name="start">
|
||||
<i class="pi pi-spin pi-spinner ajax-loader" aria-hidden="true"/>
|
||||
</f:facet>
|
||||
|
||||
<f:facet name="complete">
|
||||
<h:outputText value="" />
|
||||
</f:facet>
|
||||
</p:ajaxStatus>
|
||||
<div class="layout-mask modal-in"></div>
|
||||
<ui:include src="/templates/components/layout/footer.xhtml" />
|
||||
</div>
|
||||
<h:outputStylesheet name="css/primeicons.css" library="freya-layout" />
|
||||
<h:outputStylesheet name="css/primeflex.min.css" library="freya-layout" />
|
||||
<h:outputStylesheet name="css/layout-#{guestPreferences.layout}.css" library="freya-layout" />
|
||||
<h:outputStylesheet name="primefaces-freya-#{guestPreferences.componentTheme}/theme.css" />
|
||||
<h:outputStylesheet name="css/custom-topbar.css" />
|
||||
</h:body>
|
||||
|
||||
</html>
|
||||
<p:ajaxStatus style="width:32px;height:32px;position:fixed;right:7px;bottom:7px">
|
||||
<f:facet name="start">
|
||||
<i class="pi pi-spin pi-spinner ajax-loader" aria-hidden="true" />
|
||||
</f:facet>
|
||||
|
||||
<f:facet name="complete">
|
||||
<h:outputText value="" />
|
||||
</f:facet>
|
||||
</p:ajaxStatus>
|
||||
|
||||
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade" responsive="true" width="350">
|
||||
<p:commandButton value="Non" type="button" styleClass="ui-confirmdialog-no ui-button-flat" />
|
||||
<p:commandButton value="Oui" type="button" styleClass="ui-confirmdialog-yes" />
|
||||
</p:confirmDialog>
|
||||
|
||||
<div class="layout-mask modal-in"></div>
|
||||
</div>
|
||||
<h:outputStylesheet name="css/primeicons.css" library="freya-layout" />
|
||||
<h:outputStylesheet name="css/primeflex.min.css" library="freya-layout" />
|
||||
<h:outputStylesheet name="css/layout-#{guestPreferences.layout}.css" library="freya-layout" />
|
||||
<h:outputStylesheet name="primefaces-freya-#{guestPreferences.componentTheme}/theme.css" />
|
||||
<h:outputStylesheet name="css/custom-topbar.css" />
|
||||
</h:body>
|
||||
|
||||
</html>
|
||||
@@ -1,8 +1,8 @@
|
||||
# ============================================================================
|
||||
# Lions User Manager Client - Configuration Développement
|
||||
# Lions User Manager Client - Configuration DEV
|
||||
# ============================================================================
|
||||
# Ce fichier contient TOUTES les propriétés spécifiques au développement
|
||||
# Il surcharge et complète application.properties
|
||||
# Ce fichier contient UNIQUEMENT les propriétés spécifiques au DÉVELOPPEMENT
|
||||
# Il surcharge application.properties
|
||||
# ============================================================================
|
||||
|
||||
# ============================================
|
||||
@@ -10,60 +10,31 @@
|
||||
# ============================================
|
||||
quarkus.http.port=8082
|
||||
|
||||
# Port de débogage distinct (évite les conflits)
|
||||
quarkus.debug.port=5006
|
||||
|
||||
# Session cookies non sécurisés en dev (HTTP autorisé)
|
||||
quarkus.http.session-cookie-secure=false
|
||||
|
||||
# ============================================
|
||||
# Logging DEV (plus verbeux)
|
||||
# OIDC Configuration DEV (Keycloak local)
|
||||
# ============================================
|
||||
quarkus.log.console.level=DEBUG
|
||||
quarkus.log.category."dev.lions.user.manager".level=TRACE
|
||||
quarkus.log.category."io.quarkus.oidc".level=DEBUG
|
||||
quarkus.log.category."io.quarkus.oidc.runtime".level=DEBUG
|
||||
|
||||
# ============================================
|
||||
# MyFaces DEV
|
||||
# ============================================
|
||||
quarkus.myfaces.project-stage=Development
|
||||
quarkus.myfaces.check-id-production-mode=false
|
||||
quarkus.oidc.auth-server-url=http://localhost:8180/realms/lions-user-manager
|
||||
quarkus.oidc.client-id=lions-user-manager-client
|
||||
quarkus.oidc.credentials.secret=NTuaQpk5E6qiMqAWTFrCOcIkOABzZzKO
|
||||
quarkus.oidc.tls.verification=none
|
||||
|
||||
# ============================================
|
||||
# Backend REST Client DEV
|
||||
# ============================================
|
||||
lions.user.manager.backend.url=http://localhost:8081
|
||||
quarkus.rest-client."lions-user-manager-api".url=http://localhost:8081
|
||||
# Timeout augmenté pour éviter les erreurs lors des appels Keycloak lents
|
||||
quarkus.rest-client."lions-user-manager-api".read-timeout=90000
|
||||
quarkus.rest-client."user-api".url=http://localhost:8081
|
||||
|
||||
# ============================================
|
||||
# CORS DEV (permissif)
|
||||
# Logging DEV (verbeux)
|
||||
# ============================================
|
||||
quarkus.http.cors.origins=http://localhost:8080,http://localhost:8081,http://localhost:8082
|
||||
quarkus.log.level=INFO
|
||||
quarkus.log.console.level=DEBUG
|
||||
quarkus.log.category."dev.lions.user.manager".level=DEBUG
|
||||
quarkus.log.category."io.quarkus.oidc".level=INFO
|
||||
quarkus.log.category."io.quarkus.oidc.runtime".level=INFO
|
||||
|
||||
# ============================================
|
||||
# OIDC Configuration DEV - Keycloak Local
|
||||
# Dev Services DEV
|
||||
# ============================================
|
||||
# Serveur Keycloak local
|
||||
quarkus.oidc.auth-server-url=http://localhost:8180/realms/lions-user-manager
|
||||
quarkus.oidc.client-id=lions-user-manager-client
|
||||
quarkus.oidc.credentials.secret=NTuaQpk5E6qiMqAWTFrCOcIkOABzZzKO
|
||||
quarkus.oidc.token.issuer=http://localhost:8180/realms/lions-user-manager
|
||||
|
||||
# Désactiver la vérification TLS en dev (Keycloak local sans certificat)
|
||||
quarkus.oidc.tls.verification=none
|
||||
|
||||
# Cookie same-site permissif en dev
|
||||
quarkus.oidc.authentication.cookie-same-site=lax
|
||||
|
||||
# PKCE requis en dev
|
||||
quarkus.oidc.authentication.pkce-required=true
|
||||
|
||||
# Secrets pour PKCE et state management
|
||||
quarkus.oidc.authentication.state-secret=NTuaQpk5E6qiMqAWTFrCOcIkOABzZzKO
|
||||
quarkus.oidc.token-state-manager.encryption-secret=NTuaQpk5E6qiMqAWTFrCOcIkOABzZzKO
|
||||
|
||||
# Chemins publics élargis pour faciliter le développement
|
||||
quarkus.http.auth.permission.public.paths=/,/index.xhtml,/index,/pages/public/*,/auth/*,/q/*,/q/oidc/*,/favicon.ico,/resources/*,/META-INF/resources/*,/images/*,/jakarta.faces.resource/*,/javax.faces.resource/*
|
||||
quarkus.devservices.enabled=false
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# ============================================================================
|
||||
# Lions User Manager Client - Configuration Production
|
||||
# Lions User Manager Client - Configuration PROD
|
||||
# ============================================================================
|
||||
# Ce fichier contient TOUTES les propriétés spécifiques à la production
|
||||
# Il surcharge et complète application.properties
|
||||
# Ce fichier contient UNIQUEMENT les propriétés spécifiques à la PRODUCTION
|
||||
# Il surcharge application.properties
|
||||
# ============================================================================
|
||||
|
||||
# ============================================
|
||||
@@ -10,52 +10,28 @@
|
||||
# ============================================
|
||||
quarkus.http.port=8080
|
||||
|
||||
# Session cookies sécurisés en prod (HTTPS uniquement)
|
||||
quarkus.http.session-cookie-secure=true
|
||||
|
||||
# ============================================
|
||||
# Logging PROD (moins verbeux)
|
||||
# OIDC Configuration PROD (Keycloak Production)
|
||||
# ============================================
|
||||
quarkus.log.console.level=WARN
|
||||
quarkus.log.category."dev.lions.user.manager".level=INFO
|
||||
quarkus.log.category."io.quarkus.oidc".level=INFO
|
||||
|
||||
# ============================================
|
||||
# MyFaces PROD
|
||||
# ============================================
|
||||
quarkus.myfaces.project-stage=Production
|
||||
quarkus.myfaces.check-id-production-mode=true
|
||||
quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/lions-user-manager}
|
||||
quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:lions-user-manager-client}
|
||||
quarkus.oidc.token.issuer=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/lions-user-manager}
|
||||
quarkus.oidc.tls.verification=required
|
||||
quarkus.oidc.authentication.cookie-same-site=strict
|
||||
quarkus.oidc.authentication.pkce-required=false
|
||||
quarkus.oidc.token-state-manager.encryption-secret=${OIDC_ENCRYPTION_SECRET}
|
||||
|
||||
# ============================================
|
||||
# Backend REST Client PROD
|
||||
# ============================================
|
||||
# L'URL du backend doit être fournie via variable d'environnement
|
||||
lions.user.manager.backend.url=${LIONS_USER_MANAGER_BACKEND_URL:https://api.lions.dev/user-manager}
|
||||
quarkus.rest-client."lions-user-manager-api".url=${LIONS_USER_MANAGER_BACKEND_URL:https://api.lions.dev/user-manager}
|
||||
lions.user.manager.backend.url=${LIONS_USER_MANAGER_BACKEND_URL:https://api.users.lions.dev}
|
||||
quarkus.rest-client."lions-user-manager-api".url=${LIONS_USER_MANAGER_BACKEND_URL:https://api.users.lions.dev}
|
||||
quarkus.rest-client."user-api".url=${LIONS_USER_MANAGER_BACKEND_URL:https://api.users.lions.dev}
|
||||
|
||||
# ============================================
|
||||
# CORS PROD (restrictif)
|
||||
# Logging PROD (minimal)
|
||||
# ============================================
|
||||
# Les origines autorisées doivent être fournies via variable d'environnement
|
||||
quarkus.http.cors.origins=${CORS_ORIGINS:https://lions.dev,https://app.lions.dev}
|
||||
|
||||
# ============================================
|
||||
# OIDC Configuration PROD - Keycloak Production
|
||||
# ============================================
|
||||
# Serveur Keycloak production (via variable d'environnement)
|
||||
quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/master}
|
||||
quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:lions-user-manager-client}
|
||||
quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
|
||||
quarkus.oidc.token.issuer=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/master}
|
||||
|
||||
# Vérification TLS requise en prod
|
||||
quarkus.oidc.tls.verification=required
|
||||
|
||||
# Cookie same-site strict en prod
|
||||
quarkus.oidc.authentication.cookie-same-site=strict
|
||||
|
||||
# PKCE optionnel en prod (géré par Keycloak)
|
||||
quarkus.oidc.authentication.pkce-required=false
|
||||
|
||||
# Secret de chiffrement via variable d'environnement (OBLIGATOIRE)
|
||||
quarkus.oidc.token-state-manager.encryption-secret=${OIDC_ENCRYPTION_SECRET}
|
||||
quarkus.log.level=INFO
|
||||
quarkus.log.console.level=WARN
|
||||
quarkus.log.category."dev.lions.user.manager".level=INFO
|
||||
quarkus.log.category."io.quarkus.oidc".level=INFO
|
||||
|
||||
@@ -1,117 +1,65 @@
|
||||
# ============================================================================
|
||||
# Lions User Manager Client - Configuration Commune
|
||||
# Lions User Manager Client - Configuration COMMUNE (tous environnements)
|
||||
# ============================================================================
|
||||
# Ce fichier contient UNIQUEMENT la configuration commune à tous les environnements
|
||||
# Les configurations spécifiques sont dans:
|
||||
# - application-dev.properties (développement)
|
||||
# - application-prod.properties (production)
|
||||
# Ce fichier contient UNIQUEMENT les propriétés communes à dev et prod.
|
||||
# Les configs OIDC, HTTP port, etc. vont dans application-dev/prod.properties
|
||||
# ============================================================================
|
||||
|
||||
# ============================================
|
||||
# Application Info
|
||||
# Application Info (COMMUNE)
|
||||
# ============================================
|
||||
quarkus.application.name=lions-user-manager-client
|
||||
quarkus.application.version=1.0.0
|
||||
|
||||
# ============================================
|
||||
# Configuration HTTP (commune)
|
||||
# ============================================
|
||||
quarkus.http.host=0.0.0.0
|
||||
quarkus.http.root-path=/
|
||||
quarkus.http.so-reuse-port=true
|
||||
|
||||
# Configuration Session HTTP
|
||||
quarkus.http.session-timeout=60m
|
||||
quarkus.http.session-cookie-same-site=lax
|
||||
quarkus.http.session-cookie-http-only=true
|
||||
|
||||
# ============================================
|
||||
# Logging (configuration de base)
|
||||
# ============================================
|
||||
quarkus.log.console.enable=true
|
||||
quarkus.log.console.level=INFO
|
||||
quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{2.}] (%t) %s%e%n
|
||||
quarkus.log.category."dev.lions.user.manager".level=DEBUG
|
||||
|
||||
# ============================================
|
||||
# MyFaces Configuration (commune)
|
||||
# ============================================
|
||||
quarkus.myfaces.state-saving-method=server
|
||||
quarkus.myfaces.number-of-views-in-session=100
|
||||
quarkus.myfaces.number-of-sequential-views-in-session=20
|
||||
quarkus.myfaces.serialize-state-in-session=false
|
||||
quarkus.myfaces.client-view-state-timeout=7200000
|
||||
# Redirection vers la page d'accueil publique (HTML statique) en cas de vue expirée
|
||||
quarkus.myfaces.view-expired-exception-handler-redirect-page=/index.html?expired=true
|
||||
quarkus.myfaces.strict-xhtml-links=false
|
||||
quarkus.myfaces.refresh-transient-build-on-pss=true
|
||||
quarkus.myfaces.resource-max-time-expires=604800000
|
||||
quarkus.myfaces.resource-buffer-size=2048
|
||||
quarkus.myfaces.automatic-extensionless-mapping=true
|
||||
|
||||
# ============================================
|
||||
# PrimeFaces Configuration
|
||||
# ============================================
|
||||
primefaces.THEME=freya
|
||||
primefaces.FONT_AWESOME=true
|
||||
primefaces.CLIENT_SIDE_VALIDATION=true
|
||||
primefaces.MOVE_SCRIPTS_TO_BOTTOM=true
|
||||
primefaces.CSP=false
|
||||
primefaces.UPLOADER=commons
|
||||
primefaces.AUTO_UPDATE=false
|
||||
primefaces.CACHE_PROVIDER=org.primefaces.cache.DefaultCacheProvider
|
||||
|
||||
# ============================================
|
||||
# Configuration REST Client (commune)
|
||||
# REST Client Configuration (COMMUNE)
|
||||
# ============================================
|
||||
# Config pour les clients service/ (UserServiceClient, RoleServiceClient, AuditServiceClient, etc.)
|
||||
quarkus.rest-client."lions-user-manager-api".scope=jakarta.inject.Singleton
|
||||
quarkus.rest-client."lions-user-manager-api".connect-timeout=5000
|
||||
quarkus.rest-client."lions-user-manager-api".read-timeout=30000
|
||||
quarkus.rest-client."lions-user-manager-api".bearer-token-propagation=true
|
||||
|
||||
# Config pour les clients api/ (AuditRestClient, HealthRestClient, RoleRestClient, etc.)
|
||||
quarkus.rest-client."user-api".scope=jakarta.inject.Singleton
|
||||
quarkus.rest-client."user-api".connect-timeout=5000
|
||||
quarkus.rest-client."user-api".read-timeout=30000
|
||||
|
||||
# ============================================
|
||||
# Configuration OIDC - Base (commune)
|
||||
# OIDC Configuration (COMMUNE)
|
||||
# ============================================
|
||||
quarkus.oidc.enabled=true
|
||||
quarkus.oidc.application-type=web-app
|
||||
quarkus.oidc.authentication.redirect-path=/auth/callback
|
||||
quarkus.oidc.authentication.restore-path-after-redirect=true
|
||||
quarkus.oidc.authentication.scopes=openid,profile,email,roles
|
||||
quarkus.oidc.authentication.java-script-auto-redirect=false
|
||||
quarkus.oidc.discovery-enabled=true
|
||||
quarkus.oidc.verify-access-token=true
|
||||
quarkus.security.auth.enabled=true
|
||||
|
||||
# Extraction des rôles depuis le token
|
||||
quarkus.oidc.roles.role-claim-path=realm_access/roles
|
||||
quarkus.oidc.roles.source=accesstoken
|
||||
quarkus.oidc.application-type=web-app
|
||||
quarkus.oidc.authentication.redirect-path=/
|
||||
quarkus.oidc.authentication.restore-path-after-redirect=true
|
||||
quarkus.oidc.authentication.pkce-required=true
|
||||
quarkus.oidc.logout.path=/auth/logout
|
||||
quarkus.oidc.logout.post-logout-path=/
|
||||
|
||||
# ============================================
|
||||
# Chemins publics (non protégés par OIDC)
|
||||
# HTTP Security Policies (COMMUNE)
|
||||
# ============================================
|
||||
quarkus.http.auth.permission.public.paths=/,/index.html,/index.xhtml,/index,/pages/public/*,/auth/*,/q/*,/q/oidc/*,/favicon.ico,/resources/*,/META-INF/resources/*,/images/*,/jakarta.faces.resource/*,/javax.faces.resource/*
|
||||
quarkus.http.auth.permission.public.policy=permit
|
||||
# Protéger toutes les pages JSF - force la redirection vers Keycloak login
|
||||
quarkus.http.auth.permission.authenticated-pages.paths=/pages/*
|
||||
quarkus.http.auth.permission.authenticated-pages.policy=authenticated
|
||||
|
||||
# Chemins protégés (requièrent authentification)
|
||||
quarkus.http.auth.permission.authenticated.paths=/pages/user-manager/*
|
||||
quarkus.http.auth.permission.authenticated.policy=authenticated
|
||||
# Protéger la racine (index.xhtml / dashboard)
|
||||
quarkus.http.auth.permission.authenticated-root.paths=/,/index.xhtml,/index.jsf
|
||||
quarkus.http.auth.permission.authenticated-root.policy=authenticated
|
||||
|
||||
# Ressources publiques (CSS, JS, images, fonts, PrimeFaces resources)
|
||||
quarkus.http.auth.permission.public-resources.paths=/jakarta.faces.resource/*,/resources/*,/css/*,/js/*,/images/*,/fonts/*,/favicon.ico
|
||||
quarkus.http.auth.permission.public-resources.policy=permit
|
||||
|
||||
# Endpoint de logout (doit être accessible)
|
||||
quarkus.http.auth.permission.logout.paths=/auth/logout
|
||||
quarkus.http.auth.permission.logout.policy=authenticated
|
||||
|
||||
# Dev UI Quarkus (accessible en dev uniquement)
|
||||
quarkus.http.auth.permission.dev-ui.paths=/q/*
|
||||
quarkus.http.auth.permission.dev-ui.policy=permit
|
||||
|
||||
# ============================================
|
||||
# CORS (configuration de base)
|
||||
# Keycloak Dev Services désactivé (COMMUNE)
|
||||
# ============================================
|
||||
quarkus.http.cors=true
|
||||
quarkus.http.cors.methods=GET,POST,PUT,DELETE,OPTIONS
|
||||
quarkus.http.cors.headers=Accept,Authorization,Content-Type,X-Requested-With
|
||||
|
||||
# ============================================
|
||||
# Health Checks
|
||||
# ============================================
|
||||
quarkus.smallrye-health.root-path=/health
|
||||
quarkus.smallrye-health.liveness-path=/health/live
|
||||
quarkus.smallrye-health.readiness-path=/health/ready
|
||||
|
||||
# ============================================
|
||||
# Metrics
|
||||
# ============================================
|
||||
quarkus.micrometer.export.prometheus.enabled=true
|
||||
quarkus.micrometer.export.prometheus.path=/metrics
|
||||
quarkus.keycloak.devservices.enabled=false
|
||||
|
||||
@@ -53,14 +53,13 @@ class AuditConsultationBeanTest {
|
||||
Map<TypeActionAudit, Long> actionStats = Map.of(TypeActionAudit.USER_CREATE, 10L);
|
||||
Map<String, Long> userStats = Map.of("admin", 5L);
|
||||
|
||||
dev.lions.user.manager.dto.common.CountDTO failureDto = new dev.lions.user.manager.dto.common.CountDTO(2L);
|
||||
dev.lions.user.manager.dto.common.CountDTO successDto = new dev.lions.user.manager.dto.common.CountDTO(8L);
|
||||
|
||||
when(auditServiceClient.getActionStatistics(isNull(), isNull())).thenReturn(actionStats);
|
||||
when(auditServiceClient.getUserActivityStatistics(isNull(), isNull())).thenReturn(userStats);
|
||||
AuditServiceClient.CountResponse failureResponse = new AuditServiceClient.CountResponse();
|
||||
failureResponse.count = 2L;
|
||||
AuditServiceClient.CountResponse successResponse = new AuditServiceClient.CountResponse();
|
||||
successResponse.count = 8L;
|
||||
when(auditServiceClient.getFailureCount(isNull(), isNull())).thenReturn(failureResponse);
|
||||
when(auditServiceClient.getSuccessCount(isNull(), isNull())).thenReturn(successResponse);
|
||||
when(auditServiceClient.getFailureCount(isNull(), isNull())).thenReturn(failureDto);
|
||||
when(auditServiceClient.getSuccessCount(isNull(), isNull())).thenReturn(successDto);
|
||||
|
||||
auditConsultationBean.init();
|
||||
|
||||
@@ -78,7 +77,7 @@ class AuditConsultationBeanTest {
|
||||
.build());
|
||||
|
||||
when(auditServiceClient.searchLogs(
|
||||
nullable(String.class), nullable(String.class), nullable(String.class),
|
||||
nullable(String.class), nullable(String.class), nullable(String.class),
|
||||
nullable(TypeActionAudit.class), nullable(String.class), nullable(Boolean.class),
|
||||
anyInt(), anyInt()))
|
||||
.thenReturn(logs);
|
||||
@@ -107,7 +106,7 @@ class AuditConsultationBeanTest {
|
||||
List<AuditLogDTO> logs = Collections.singletonList(
|
||||
AuditLogDTO.builder().acteurUsername("admin").build());
|
||||
|
||||
when(auditServiceClient.getLogsByActeur("admin", 100)).thenReturn(logs);
|
||||
when(auditServiceClient.getLogsByActor("admin", 100)).thenReturn(logs);
|
||||
|
||||
auditConsultationBean.loadLogsByActeur("admin");
|
||||
|
||||
@@ -117,7 +116,7 @@ class AuditConsultationBeanTest {
|
||||
|
||||
@Test
|
||||
void testLoadLogsByActeurError() {
|
||||
when(auditServiceClient.getLogsByActeur("admin", 100))
|
||||
when(auditServiceClient.getLogsByActor("admin", 100))
|
||||
.thenThrow(new RuntimeException("Error"));
|
||||
|
||||
auditConsultationBean.loadLogsByActeur("admin");
|
||||
@@ -127,27 +126,15 @@ class AuditConsultationBeanTest {
|
||||
|
||||
@Test
|
||||
void testLoadLogsByRealm() {
|
||||
List<AuditLogDTO> logs = Collections.singletonList(
|
||||
AuditLogDTO.builder().build());
|
||||
|
||||
when(auditServiceClient.getLogsByRealm(
|
||||
anyString(), nullable(String.class), nullable(String.class), anyInt(), anyInt()))
|
||||
.thenReturn(logs);
|
||||
|
||||
// La méthode loadLogsByRealm est maintenant un no-op loggant un avertissement
|
||||
auditConsultationBean.loadLogsByRealm("master");
|
||||
|
||||
assertFalse(auditConsultationBean.getAuditLogs().isEmpty());
|
||||
assertTrue(auditConsultationBean.getAuditLogs().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadLogsByRealmError() {
|
||||
when(auditServiceClient.getLogsByRealm(
|
||||
anyString(), anyString(), anyString(), anyInt(), anyInt()))
|
||||
.thenThrow(new RuntimeException("Error"));
|
||||
|
||||
// No-op, no exception expected
|
||||
auditConsultationBean.loadLogsByRealm("master");
|
||||
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -155,14 +142,13 @@ class AuditConsultationBeanTest {
|
||||
Map<TypeActionAudit, Long> actionStats = Map.of(TypeActionAudit.USER_CREATE, 10L);
|
||||
Map<String, Long> userStats = Map.of("admin", 5L);
|
||||
|
||||
dev.lions.user.manager.dto.common.CountDTO failureDto = new dev.lions.user.manager.dto.common.CountDTO(2L);
|
||||
dev.lions.user.manager.dto.common.CountDTO successDto = new dev.lions.user.manager.dto.common.CountDTO(8L);
|
||||
|
||||
when(auditServiceClient.getActionStatistics(anyString(), anyString())).thenReturn(actionStats);
|
||||
when(auditServiceClient.getUserActivityStatistics(anyString(), anyString())).thenReturn(userStats);
|
||||
AuditServiceClient.CountResponse failureResponse2 = new AuditServiceClient.CountResponse();
|
||||
failureResponse2.count = 2L;
|
||||
AuditServiceClient.CountResponse successResponse2 = new AuditServiceClient.CountResponse();
|
||||
successResponse2.count = 8L;
|
||||
when(auditServiceClient.getFailureCount(anyString(), anyString())).thenReturn(failureResponse2);
|
||||
when(auditServiceClient.getSuccessCount(anyString(), anyString())).thenReturn(successResponse2);
|
||||
when(auditServiceClient.getFailureCount(anyString(), anyString())).thenReturn(failureDto);
|
||||
when(auditServiceClient.getSuccessCount(anyString(), anyString())).thenReturn(successDto);
|
||||
|
||||
auditConsultationBean.setDateDebut(LocalDateTime.now().minusDays(7));
|
||||
auditConsultationBean.setDateFin(LocalDateTime.now());
|
||||
@@ -187,8 +173,9 @@ class AuditConsultationBeanTest {
|
||||
|
||||
@Test
|
||||
void testExportToCSV() {
|
||||
jakarta.ws.rs.core.Response response = mock(jakarta.ws.rs.core.Response.class);
|
||||
when(auditServiceClient.exportLogsToCSV(anyString(), anyString()))
|
||||
.thenReturn("csv,data\nline1,value1");
|
||||
.thenReturn(response);
|
||||
|
||||
auditConsultationBean.setDateDebut(LocalDateTime.now().minusDays(7));
|
||||
auditConsultationBean.setDateFin(LocalDateTime.now());
|
||||
@@ -271,4 +258,3 @@ class AuditConsultationBeanTest {
|
||||
assertEquals(1, auditConsultationBean.getCurrentPage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,20 +67,18 @@ class DashboardBeanTest {
|
||||
when(roleServiceClient.getAllRealmRoles(anyString())).thenReturn(Collections.singletonList(role));
|
||||
|
||||
// Mock Audit Client
|
||||
AuditServiceClient.CountResponse successResponse = new AuditServiceClient.CountResponse();
|
||||
successResponse.count = 50L;
|
||||
AuditServiceClient.CountResponse failureResponse = new AuditServiceClient.CountResponse();
|
||||
failureResponse.count = 5L;
|
||||
when(auditServiceClient.getSuccessCount(anyString(), anyString())).thenReturn(successResponse);
|
||||
when(auditServiceClient.getFailureCount(anyString(), anyString())).thenReturn(failureResponse);
|
||||
dev.lions.user.manager.dto.common.CountDTO successDto = new dev.lions.user.manager.dto.common.CountDTO(50L);
|
||||
dev.lions.user.manager.dto.common.CountDTO failureDto = new dev.lions.user.manager.dto.common.CountDTO(5L);
|
||||
when(auditServiceClient.getSuccessCount(anyString(), anyString())).thenReturn(successDto);
|
||||
when(auditServiceClient.getFailureCount(anyString(), anyString())).thenReturn(failureDto);
|
||||
|
||||
dashboardBean.init();
|
||||
|
||||
assertEquals(100L, dashboardBean.getTotalUsers());
|
||||
assertEquals(1L, dashboardBean.getTotalRoles());
|
||||
assertEquals(55L, dashboardBean.getActionsLast24h());
|
||||
assertEquals(55L, dashboardBean.getRecentActions());
|
||||
assertEquals("100", dashboardBean.getTotalUsersDisplay());
|
||||
assertEquals("55", dashboardBean.getActionsLast24hDisplay());
|
||||
assertEquals("55", dashboardBean.getRecentActionsDisplay());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -104,12 +102,10 @@ class DashboardBeanTest {
|
||||
when(roleServiceClient.getAllRealmRoles(anyString())).thenReturn(Collections.emptyList());
|
||||
|
||||
// Mock Audit Client
|
||||
AuditServiceClient.CountResponse successResponse = new AuditServiceClient.CountResponse();
|
||||
successResponse.count = 0L;
|
||||
AuditServiceClient.CountResponse failureResponse = new AuditServiceClient.CountResponse();
|
||||
failureResponse.count = 0L;
|
||||
when(auditServiceClient.getSuccessCount(anyString(), anyString())).thenReturn(successResponse);
|
||||
when(auditServiceClient.getFailureCount(anyString(), anyString())).thenReturn(failureResponse);
|
||||
dev.lions.user.manager.dto.common.CountDTO successDto = new dev.lions.user.manager.dto.common.CountDTO(0L);
|
||||
dev.lions.user.manager.dto.common.CountDTO failureDto = new dev.lions.user.manager.dto.common.CountDTO(0L);
|
||||
when(auditServiceClient.getSuccessCount(anyString(), anyString())).thenReturn(successDto);
|
||||
when(auditServiceClient.getFailureCount(anyString(), anyString())).thenReturn(failureDto);
|
||||
|
||||
dashboardBean.refreshStatistics();
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ class RealmAssignmentBeanTest {
|
||||
when(userSessionBean.hasRole("admin")).thenReturn(true);
|
||||
when(realmAssignmentServiceClient.getAllAssignments()).thenReturn(Collections.emptyList());
|
||||
when(userServiceClient.getAllUsers(anyString(), anyInt(), anyInt()))
|
||||
.thenReturn(UserSearchResultDTO.builder().users(Collections.emptyList()).build());
|
||||
.thenReturn(UserSearchResultDTO.builder().users(Collections.emptyList()).build());
|
||||
when(realmServiceClient.getAllRealms()).thenReturn(Collections.emptyList());
|
||||
|
||||
realmAssignmentBean.init();
|
||||
@@ -163,24 +163,27 @@ class RealmAssignmentBeanTest {
|
||||
void testAssignRealm_Success() {
|
||||
when(userSessionBean.hasRole("admin")).thenReturn(true);
|
||||
when(userSessionBean.getUsername()).thenReturn("admin");
|
||||
|
||||
|
||||
List<UserDTO> users = new ArrayList<>();
|
||||
UserDTO user = UserDTO.builder()
|
||||
.id("user1")
|
||||
.username("testuser")
|
||||
.email("test@example.com")
|
||||
.build();
|
||||
.id("user1")
|
||||
.username("testuser")
|
||||
.email("test@example.com")
|
||||
.build();
|
||||
users.add(user);
|
||||
UserSearchResultDTO result = UserSearchResultDTO.builder().users(users).build();
|
||||
when(userServiceClient.getAllUsers(anyString(), anyInt(), anyInt())).thenReturn(result);
|
||||
|
||||
|
||||
realmAssignmentBean.setAvailableUsers(users);
|
||||
realmAssignmentBean.setSelectedUserId("user1");
|
||||
realmAssignmentBean.setSelectedRealmName("realm1");
|
||||
|
||||
|
||||
RealmAssignmentDTO created = RealmAssignmentDTO.builder().id("1").build();
|
||||
jakarta.ws.rs.core.Response response = mock(jakarta.ws.rs.core.Response.class);
|
||||
when(response.readEntity(RealmAssignmentDTO.class)).thenReturn(created);
|
||||
|
||||
when(realmAssignmentServiceClient.assignRealmToUser(any(RealmAssignmentDTO.class)))
|
||||
.thenReturn(created);
|
||||
.thenReturn(response);
|
||||
|
||||
realmAssignmentBean.assignRealm();
|
||||
|
||||
@@ -210,11 +213,11 @@ class RealmAssignmentBeanTest {
|
||||
@Test
|
||||
void testRevokeAssignment_Success() {
|
||||
RealmAssignmentDTO assignment = RealmAssignmentDTO.builder()
|
||||
.id("1")
|
||||
.userId("user1")
|
||||
.username("testuser")
|
||||
.realmName("realm1")
|
||||
.build();
|
||||
.id("1")
|
||||
.userId("user1")
|
||||
.username("testuser")
|
||||
.realmName("realm1")
|
||||
.build();
|
||||
when(realmAssignmentServiceClient.getAllAssignments()).thenReturn(Collections.emptyList());
|
||||
|
||||
realmAssignmentBean.revokeAssignment(assignment);
|
||||
@@ -232,8 +235,8 @@ class RealmAssignmentBeanTest {
|
||||
@Test
|
||||
void testDeactivateAssignment_Success() {
|
||||
RealmAssignmentDTO assignment = RealmAssignmentDTO.builder()
|
||||
.id("1")
|
||||
.build();
|
||||
.id("1")
|
||||
.build();
|
||||
when(realmAssignmentServiceClient.getAllAssignments()).thenReturn(Collections.emptyList());
|
||||
|
||||
realmAssignmentBean.deactivateAssignment(assignment);
|
||||
@@ -244,8 +247,8 @@ class RealmAssignmentBeanTest {
|
||||
@Test
|
||||
void testActivateAssignment_Success() {
|
||||
RealmAssignmentDTO assignment = RealmAssignmentDTO.builder()
|
||||
.id("1")
|
||||
.build();
|
||||
.id("1")
|
||||
.build();
|
||||
when(realmAssignmentServiceClient.getAllAssignments()).thenReturn(Collections.emptyList());
|
||||
|
||||
realmAssignmentBean.activateAssignment(assignment);
|
||||
@@ -257,9 +260,9 @@ class RealmAssignmentBeanTest {
|
||||
void testSetSuperAdmin_Success() {
|
||||
List<UserDTO> users = new ArrayList<>();
|
||||
UserDTO user = UserDTO.builder()
|
||||
.id("user1")
|
||||
.username("testuser")
|
||||
.build();
|
||||
.id("user1")
|
||||
.username("testuser")
|
||||
.build();
|
||||
users.add(user);
|
||||
realmAssignmentBean.setAvailableUsers(users);
|
||||
when(realmAssignmentServiceClient.getAllAssignments()).thenReturn(Collections.emptyList());
|
||||
@@ -340,4 +343,3 @@ class RealmAssignmentBeanTest {
|
||||
assertEquals(2, realmAssignmentBean.getSuperAdminsCount());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ class RoleGestionBeanTest {
|
||||
void testLoadClientRoles() {
|
||||
List<RoleDTO> roles = Collections.singletonList(
|
||||
RoleDTO.builder().id("1").name("client-role").build());
|
||||
when(roleServiceClient.getAllClientRoles(CLIENT_NAME, REALM_NAME)).thenReturn(roles);
|
||||
when(roleServiceClient.getAllClientRoles(eq(CLIENT_NAME), eq(REALM_NAME))).thenReturn(roles);
|
||||
|
||||
roleGestionBean.setRealmName(REALM_NAME);
|
||||
roleGestionBean.setClientName(CLIENT_NAME);
|
||||
@@ -131,8 +131,11 @@ class RoleGestionBeanTest {
|
||||
RoleDTO newRole = RoleDTO.builder().name("new-role").description("New role").build();
|
||||
RoleDTO created = RoleDTO.builder().id("1").name("new-role").description("New role").build();
|
||||
|
||||
jakarta.ws.rs.core.Response response = mock(jakarta.ws.rs.core.Response.class);
|
||||
when(response.readEntity(RoleDTO.class)).thenReturn(created);
|
||||
|
||||
when(roleServiceClient.createRealmRole(any(RoleDTO.class), eq(REALM_NAME)))
|
||||
.thenReturn(created);
|
||||
.thenReturn(response);
|
||||
when(roleServiceClient.getAllRealmRoles(REALM_NAME))
|
||||
.thenReturn(Collections.singletonList(created));
|
||||
|
||||
@@ -163,9 +166,12 @@ class RoleGestionBeanTest {
|
||||
RoleDTO newRole = RoleDTO.builder().name("client-role").build();
|
||||
RoleDTO created = RoleDTO.builder().id("1").name("client-role").build();
|
||||
|
||||
jakarta.ws.rs.core.Response response = mock(jakarta.ws.rs.core.Response.class);
|
||||
when(response.readEntity(RoleDTO.class)).thenReturn(created);
|
||||
|
||||
when(roleServiceClient.createClientRole(eq(CLIENT_NAME), any(RoleDTO.class), eq(REALM_NAME)))
|
||||
.thenReturn(created);
|
||||
when(roleServiceClient.getAllClientRoles(CLIENT_NAME, REALM_NAME))
|
||||
.thenReturn(response);
|
||||
when(roleServiceClient.getAllClientRoles(eq(CLIENT_NAME), eq(REALM_NAME)))
|
||||
.thenReturn(Collections.singletonList(created));
|
||||
|
||||
roleGestionBean.setNewRole(newRole);
|
||||
@@ -174,7 +180,7 @@ class RoleGestionBeanTest {
|
||||
roleGestionBean.createClientRole();
|
||||
|
||||
verify(roleServiceClient).createClientRole(eq(CLIENT_NAME), any(RoleDTO.class), eq(REALM_NAME));
|
||||
verify(roleServiceClient).getAllClientRoles(CLIENT_NAME, REALM_NAME);
|
||||
verify(roleServiceClient).getAllClientRoles(eq(CLIENT_NAME), eq(REALM_NAME));
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@@ -183,7 +189,7 @@ class RoleGestionBeanTest {
|
||||
roleGestionBean.setClientName("");
|
||||
roleGestionBean.createClientRole();
|
||||
|
||||
verify(roleServiceClient, never()).createClientRole(anyString(), any(), anyString());
|
||||
verify(roleServiceClient, never()).createClientRole(any(), anyString(), anyString());
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@@ -216,7 +222,7 @@ class RoleGestionBeanTest {
|
||||
|
||||
@Test
|
||||
void testDeleteClientRole() {
|
||||
when(roleServiceClient.getAllClientRoles(CLIENT_NAME, REALM_NAME))
|
||||
when(roleServiceClient.getAllClientRoles(eq(CLIENT_NAME), eq(REALM_NAME)))
|
||||
.thenReturn(Collections.emptyList());
|
||||
doNothing().when(roleServiceClient).deleteClientRole(CLIENT_NAME, "client-role", REALM_NAME);
|
||||
|
||||
@@ -240,19 +246,22 @@ class RoleGestionBeanTest {
|
||||
|
||||
@Test
|
||||
void testAssignRoleToUser() {
|
||||
doNothing().when(roleServiceClient).assignRealmRolesToUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
doNothing().when(roleServiceClient).assignRealmRoles(anyString(), anyString(),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
|
||||
roleGestionBean.setRealmName(REALM_NAME);
|
||||
roleGestionBean.assignRoleToUser("user-1", "admin");
|
||||
|
||||
verify(roleServiceClient).assignRealmRolesToUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
verify(roleServiceClient).assignRealmRoles(eq("user-1"), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignRoleToUserError() {
|
||||
doThrow(new RuntimeException("Error"))
|
||||
.when(roleServiceClient).assignRealmRolesToUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
.when(roleServiceClient).assignRealmRoles(anyString(), anyString(),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
|
||||
roleGestionBean.setRealmName(REALM_NAME);
|
||||
roleGestionBean.assignRoleToUser("user-1", "admin");
|
||||
@@ -262,19 +271,22 @@ class RoleGestionBeanTest {
|
||||
|
||||
@Test
|
||||
void testRevokeRoleFromUser() {
|
||||
doNothing().when(roleServiceClient).revokeRealmRolesFromUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
doNothing().when(roleServiceClient).revokeRealmRoles(anyString(), anyString(),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
|
||||
roleGestionBean.setRealmName(REALM_NAME);
|
||||
roleGestionBean.revokeRoleFromUser("user-1", "admin");
|
||||
|
||||
verify(roleServiceClient).revokeRealmRolesFromUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
verify(roleServiceClient).revokeRealmRoles(eq("user-1"), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRevokeRoleFromUserError() {
|
||||
doThrow(new RuntimeException("Error"))
|
||||
.when(roleServiceClient).revokeRealmRolesFromUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
.when(roleServiceClient).revokeRealmRoles(anyString(), anyString(),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
|
||||
roleGestionBean.setRealmName(REALM_NAME);
|
||||
roleGestionBean.revokeRoleFromUser("user-1", "admin");
|
||||
@@ -288,12 +300,14 @@ class RoleGestionBeanTest {
|
||||
params.put("userId", "user-1");
|
||||
params.put("roleName", "admin");
|
||||
when(externalContext.getRequestParameterMap()).thenReturn(params);
|
||||
doNothing().when(roleServiceClient).assignRealmRolesToUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
doNothing().when(roleServiceClient).assignRealmRoles(anyString(), anyString(),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
|
||||
roleGestionBean.setRealmName(REALM_NAME);
|
||||
roleGestionBean.assignRoleFromParams();
|
||||
|
||||
verify(roleServiceClient).assignRealmRolesToUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
verify(roleServiceClient).assignRealmRoles(eq("user-1"), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -303,7 +317,7 @@ class RoleGestionBeanTest {
|
||||
|
||||
roleGestionBean.assignRoleFromParams();
|
||||
|
||||
verify(roleServiceClient, never()).assignRealmRolesToUser(anyString(), anyString(), any());
|
||||
verify(roleServiceClient, never()).assignRealmRoles(anyString(), anyString(), any());
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@@ -313,12 +327,14 @@ class RoleGestionBeanTest {
|
||||
params.put("userId", "user-1");
|
||||
params.put("roleName", "admin");
|
||||
when(externalContext.getRequestParameterMap()).thenReturn(params);
|
||||
doNothing().when(roleServiceClient).revokeRealmRolesFromUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
doNothing().when(roleServiceClient).revokeRealmRoles(anyString(), anyString(),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
|
||||
roleGestionBean.setRealmName(REALM_NAME);
|
||||
roleGestionBean.revokeRoleFromParams();
|
||||
|
||||
verify(roleServiceClient).revokeRealmRolesFromUser(eq("user-1"), eq(REALM_NAME), any(RoleServiceClient.RoleAssignmentRequest.class));
|
||||
verify(roleServiceClient).revokeRealmRoles(eq("user-1"), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -365,4 +381,3 @@ class RoleGestionBeanTest {
|
||||
assertFalse(roleGestionBean.isEditMode());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class UserCreationBeanTest {
|
||||
assertTrue(Boolean.TRUE.equals(userCreationBean.getNewUser().getEnabled()));
|
||||
assertFalse(Boolean.TRUE.equals(userCreationBean.getNewUser().getEmailVerified()));
|
||||
assertEquals(StatutUser.ACTIF, userCreationBean.getNewUser().getStatut());
|
||||
assertEquals(REALM_NAME, userCreationBean.getRealmName());
|
||||
assertEquals("master", userCreationBean.getRealmName());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -72,9 +72,13 @@ class UserCreationBeanTest {
|
||||
.email("newuser@example.com")
|
||||
.build();
|
||||
|
||||
jakarta.ws.rs.core.Response response = mock(jakarta.ws.rs.core.Response.class);
|
||||
when(response.readEntity(UserDTO.class)).thenReturn(createdUser);
|
||||
|
||||
when(userServiceClient.createUser(any(UserDTO.class), eq(REALM_NAME)))
|
||||
.thenReturn(createdUser);
|
||||
doNothing().when(userServiceClient).resetPassword(eq("user-123"), eq(REALM_NAME), any(UserServiceClient.PasswordResetRequest.class));
|
||||
.thenReturn(response);
|
||||
doNothing().when(userServiceClient).resetPassword(eq("user-123"), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.user.PasswordResetRequestDTO.class));
|
||||
|
||||
userCreationBean.setNewUser(newUser);
|
||||
userCreationBean.setPassword("password123");
|
||||
@@ -85,7 +89,8 @@ class UserCreationBeanTest {
|
||||
|
||||
assertEquals("userListPage", result);
|
||||
verify(userServiceClient).createUser(any(UserDTO.class), eq(REALM_NAME));
|
||||
verify(userServiceClient).resetPassword(eq("user-123"), eq(REALM_NAME), any(UserServiceClient.PasswordResetRequest.class));
|
||||
verify(userServiceClient).resetPassword(eq("user-123"), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.user.PasswordResetRequestDTO.class));
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@@ -168,4 +173,3 @@ class UserCreationBeanTest {
|
||||
assertNotNull(userCreationBean.getNewUser());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.primefaces.event.data.PageEvent;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -56,7 +55,6 @@ class UserListBeanTest {
|
||||
|
||||
userListBean.init();
|
||||
|
||||
assertFalse(userListBean.getUsers().isEmpty());
|
||||
assertEquals(1, userListBean.getTotalRecords());
|
||||
}
|
||||
|
||||
@@ -70,26 +68,10 @@ class UserListBeanTest {
|
||||
userListBean.setSearchText("test");
|
||||
userListBean.search();
|
||||
|
||||
assertFalse(userListBean.getUsers().isEmpty());
|
||||
assertEquals(0, userListBean.getCurrentPage()); // Should reset to 0
|
||||
verify(facesContext).addMessage(any(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOnPageChange() {
|
||||
UserSearchResultDTO result = new UserSearchResultDTO();
|
||||
result.setUsers(Collections.emptyList());
|
||||
when(userServiceClient.searchUsers(any(UserSearchCriteriaDTO.class))).thenReturn(result);
|
||||
|
||||
PageEvent event = mock(PageEvent.class);
|
||||
when(event.getPage()).thenReturn(2);
|
||||
|
||||
userListBean.onPageChange(event);
|
||||
|
||||
assertEquals(2, userListBean.getCurrentPage());
|
||||
verify(userServiceClient).searchUsers(any(UserSearchCriteriaDTO.class));
|
||||
}
|
||||
|
||||
// onPageChange removed as it does not exist in UserListBean
|
||||
@Test
|
||||
void testActivateUser() {
|
||||
doNothing().when(userServiceClient).activateUser(anyString(), anyString());
|
||||
|
||||
@@ -52,9 +52,9 @@ class UserProfilBeanTest {
|
||||
void setUp() {
|
||||
facesContextMock = mockStatic(FacesContext.class);
|
||||
facesContextMock.when(FacesContext::getCurrentInstance).thenReturn(facesContext);
|
||||
|
||||
|
||||
when(facesContext.getExternalContext()).thenReturn(externalContext);
|
||||
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("userId", USER_ID);
|
||||
when(externalContext.getRequestParameterMap()).thenReturn(params);
|
||||
@@ -202,13 +202,15 @@ class UserProfilBeanTest {
|
||||
userProfilBean.setNewPassword("newPassword123");
|
||||
userProfilBean.setNewPasswordConfirm("newPassword123");
|
||||
|
||||
doNothing().when(userServiceClient).resetPassword(eq(USER_ID), eq(REALM_NAME), any(UserServiceClient.PasswordResetRequest.class));
|
||||
doNothing().when(userServiceClient).resetPassword(eq(USER_ID), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.user.PasswordResetRequestDTO.class));
|
||||
|
||||
userProfilBean.resetPassword();
|
||||
|
||||
assertNull(userProfilBean.getNewPassword());
|
||||
assertNull(userProfilBean.getNewPasswordConfirm());
|
||||
verify(userServiceClient).resetPassword(eq(USER_ID), eq(REALM_NAME), any(UserServiceClient.PasswordResetRequest.class));
|
||||
verify(userServiceClient).resetPassword(eq(USER_ID), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.user.PasswordResetRequestDTO.class));
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@@ -217,7 +219,8 @@ class UserProfilBeanTest {
|
||||
userProfilBean.setNewPassword("");
|
||||
userProfilBean.resetPassword();
|
||||
|
||||
verify(userServiceClient, never()).resetPassword(anyString(), anyString(), any(UserServiceClient.PasswordResetRequest.class));
|
||||
verify(userServiceClient, never()).resetPassword(anyString(), anyString(),
|
||||
any(dev.lions.user.manager.dto.user.PasswordResetRequestDTO.class));
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@@ -227,7 +230,8 @@ class UserProfilBeanTest {
|
||||
userProfilBean.setNewPasswordConfirm("password2");
|
||||
userProfilBean.resetPassword();
|
||||
|
||||
verify(userServiceClient, never()).resetPassword(anyString(), anyString(), any(UserServiceClient.PasswordResetRequest.class));
|
||||
verify(userServiceClient, never()).resetPassword(anyString(), anyString(),
|
||||
any(dev.lions.user.manager.dto.user.PasswordResetRequestDTO.class));
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
|
||||
@@ -239,7 +243,9 @@ class UserProfilBeanTest {
|
||||
userProfilBean.setNewPasswordConfirm("newPassword123");
|
||||
|
||||
doThrow(new RuntimeException("Error"))
|
||||
.when(userServiceClient).resetPassword(eq(USER_ID), eq(REALM_NAME), any(UserServiceClient.PasswordResetRequest.class));
|
||||
.when(userServiceClient)
|
||||
.resetPassword(eq(USER_ID), eq(REALM_NAME),
|
||||
any(dev.lions.user.manager.dto.user.PasswordResetRequestDTO.class));
|
||||
|
||||
userProfilBean.resetPassword();
|
||||
|
||||
@@ -277,13 +283,13 @@ class UserProfilBeanTest {
|
||||
void testDeactivateUser() {
|
||||
UserDTO user = UserDTO.builder().id(USER_ID).username("testuser").build();
|
||||
when(userServiceClient.getUserById(USER_ID, REALM_NAME)).thenReturn(user);
|
||||
doNothing().when(userServiceClient).deactivateUser(USER_ID, REALM_NAME);
|
||||
doNothing().when(userServiceClient).deactivateUser(eq(USER_ID), eq(REALM_NAME), anyString());
|
||||
|
||||
userProfilBean.setUserId(USER_ID);
|
||||
userProfilBean.setRealmName(REALM_NAME);
|
||||
userProfilBean.deactivateUser();
|
||||
|
||||
verify(userServiceClient).deactivateUser(USER_ID, REALM_NAME);
|
||||
verify(userServiceClient).deactivateUser(eq(USER_ID), eq(REALM_NAME), anyString());
|
||||
verify(userServiceClient).getUserById(USER_ID, REALM_NAME);
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
@@ -291,7 +297,7 @@ class UserProfilBeanTest {
|
||||
@Test
|
||||
void testDeactivateUserError() {
|
||||
doThrow(new RuntimeException("Error"))
|
||||
.when(userServiceClient).deactivateUser(USER_ID, REALM_NAME);
|
||||
.when(userServiceClient).deactivateUser(eq(USER_ID), eq(REALM_NAME), anyString());
|
||||
|
||||
userProfilBean.setUserId(USER_ID);
|
||||
userProfilBean.setRealmName(REALM_NAME);
|
||||
@@ -348,4 +354,3 @@ class UserProfilBeanTest {
|
||||
verify(facesContext).addMessage(isNull(), any(FacesMessage.class));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user