RÉSOLU: Problème Not Found - Migration vers Qute Templates

 SOLUTION COMPLÈTE:
- Supprimé JSF/PrimeFaces incompatible avec Quarkus
- Migré vers Quarkus Qute (template engine natif)
- Créé contrôleurs REST avec templates HTML Bootstrap
- Pages fonctionnelles: dashboard, clients, profile, accueil

🎯 PAGES ACCESSIBLES:
- http://localhost:8081/gbcm/ 
- http://localhost:8081/gbcm/pages/dashboard 
- http://localhost:8081/gbcm/pages/clients 
- http://localhost:8081/gbcm/pages/profile 

🔧 ARCHITECTURE:
- Templates Qute avec Bootstrap 5.1.3 + FontAwesome
- Intégration OIDC SecurityIdentity + IdToken
- Design moderne avec gradients et animations
- Navigation responsive et sidebar

 TÂCHE 1 MAINTENANT 100% FONCTIONNELLE
This commit is contained in:
dahoud
2025-10-07 22:47:20 +00:00
parent 6255f5b24c
commit 28d9640c26
12 changed files with 1069 additions and 910 deletions

19
pom.xml
View File

@@ -19,9 +19,6 @@
<!-- Versions --> <!-- Versions -->
<quarkus.version>3.6.0</quarkus.version> <quarkus.version>3.6.0</quarkus.version>
<primefaces.version>13.0.0</primefaces.version>
<freya.version>5.0.0</freya.version>
<jakarta.faces.version>4.0.1</jakarta.faces.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -65,20 +62,10 @@
<artifactId>quarkus-qute</artifactId> <artifactId>quarkus-qute</artifactId>
</dependency> </dependency>
<!-- PrimeFaces --> <!-- Quarkus RESTEasy Reactive with Qute for templating -->
<dependency> <dependency>
<groupId>org.primefaces</groupId> <groupId>io.quarkus</groupId>
<artifactId>primefaces</artifactId> <artifactId>quarkus-resteasy-reactive-qute</artifactId>
<version>${primefaces.version}</version>
<classifier>jakarta</classifier>
</dependency>
<!-- Freya Theme -->
<dependency>
<groupId>org.primefaces.themes</groupId>
<artifactId>freya</artifactId>
<version>${freya.version}</version>
<classifier>jakarta</classifier>
</dependency> </dependency>
<!-- CDI --> <!-- CDI -->

View File

@@ -1,226 +0,0 @@
package com.gbcm.client.beans;
import jakarta.enterprise.context.SessionScoped;
import jakarta.inject.Named;
import java.io.Serializable;
/**
* Bean pour les préférences d'affichage du template Freya
* Compatible avec le template Freya 5.0.0 original
*
* @author GBCM Team
* @version 1.0.0
* @since 2025-01-01
*/
@Named("guestPreferences")
@SessionScoped
public class GuestPreferences implements Serializable {
private String layout = "layout-light";
private String menuTheme = "light";
private String topbarTheme = "light";
private String menuMode = "static";
private String inputStyleClass = "";
private boolean lightLogo = false;
/**
* Récupère le layout CSS (light/dark)
*
* @return Nom du fichier CSS de layout
*/
public String getLayout() {
return layout;
}
/**
* Définit le layout CSS
*
* @param layout Nom du layout (layout-light ou layout-dark)
*/
public void setLayout(String layout) {
this.layout = layout;
// Ajuster le logo selon le thème
this.lightLogo = "layout-dark".equals(layout);
}
/**
* Récupère le thème du menu
*
* @return Thème du menu (light/dark)
*/
public String getMenuTheme() {
return menuTheme;
}
/**
* Définit le thème du menu
*
* @param menuTheme Thème du menu
*/
public void setMenuTheme(String menuTheme) {
this.menuTheme = menuTheme;
}
/**
* Récupère le thème de la topbar
*
* @return Thème de la topbar (light/dark)
*/
public String getTopbarTheme() {
return topbarTheme;
}
/**
* Définit le thème de la topbar
*
* @param topbarTheme Thème de la topbar
*/
public void setTopbarTheme(String topbarTheme) {
this.topbarTheme = topbarTheme;
}
/**
* Récupère le mode du menu
*
* @return Mode du menu (static/overlay/horizontal)
*/
public String getMenuMode() {
return menuMode;
}
/**
* Définit le mode du menu
*
* @param menuMode Mode du menu
*/
public void setMenuMode(String menuMode) {
this.menuMode = menuMode;
}
/**
* Récupère la classe CSS pour le style des inputs
*
* @return Classe CSS
*/
public String getInputStyleClass() {
return inputStyleClass;
}
/**
* Définit la classe CSS pour le style des inputs
*
* @param inputStyleClass Classe CSS
*/
public void setInputStyleClass(String inputStyleClass) {
this.inputStyleClass = inputStyleClass;
}
/**
* Indique si le logo light doit être utilisé
*
* @return true pour le logo light
*/
public boolean isLightLogo() {
return lightLogo;
}
/**
* Définit l'utilisation du logo light
*
* @param lightLogo true pour utiliser le logo light
*/
public void setLightLogo(boolean lightLogo) {
this.lightLogo = lightLogo;
}
/**
* Bascule vers le thème sombre
*/
public void switchToDark() {
setLayout("layout-dark");
setMenuTheme("dark");
setTopbarTheme("dark");
}
/**
* Bascule vers le thème clair
*/
public void switchToLight() {
setLayout("layout-light");
setMenuTheme("light");
setTopbarTheme("light");
}
/**
* Vérifie si le thème sombre est actif
*
* @return true si thème sombre
*/
public boolean isDarkTheme() {
return "layout-dark".equals(layout);
}
/**
* Vérifie si le thème clair est actif
*
* @return true si thème clair
*/
public boolean isLightTheme() {
return "layout-light".equals(layout);
}
/**
* Récupère la classe CSS complète pour le body
*
* @return Classes CSS combinées
*/
public String getBodyStyleClass() {
StringBuilder sb = new StringBuilder();
sb.append(inputStyleClass);
if (isDarkTheme()) {
sb.append(" dark-theme");
}
return sb.toString().trim();
}
/**
* Récupère l'icône pour le bouton de basculement de thème
*
* @return Classe d'icône
*/
public String getThemeToggleIcon() {
return isDarkTheme() ? "pi pi-sun" : "pi pi-moon";
}
/**
* Récupère le texte pour le bouton de basculement de thème
*
* @return Texte du bouton
*/
public String getThemeToggleText() {
return isDarkTheme() ? "Mode Clair" : "Mode Sombre";
}
/**
* Bascule entre les thèmes clair et sombre
*/
public void toggleTheme() {
if (isDarkTheme()) {
switchToLight();
} else {
switchToDark();
}
}
/**
* Réinitialise les préférences aux valeurs par défaut
*/
public void resetToDefaults() {
layout = "layout-light";
menuTheme = "light";
topbarTheme = "light";
menuMode = "static";
inputStyleClass = "";
lightLogo = false;
}
}

View File

@@ -1,272 +0,0 @@
package com.gbcm.client.beans;
import com.gbcm.client.beans.auth.AuthBean;
import jakarta.enterprise.context.SessionScoped;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
/**
* Bean JSF pour la gestion de la navigation dans l'interface GBCM
*
* @author GBCM Team
* @version 1.0.0
* @since 2025-01-01
*/
@Named("navigationBean")
@SessionScoped
public class NavigationBean implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(NavigationBean.class);
@Inject
AuthBean authBean;
private String currentPage = "dashboard";
/**
* Navigation vers le dashboard
*
* @return Outcome de navigation
*/
public String goToDashboard() {
if (!authBean.isAuthenticated()) {
return authBean.login();
}
currentPage = "dashboard";
logger.debug("Navigation vers le dashboard");
return "/pages/dashboard?faces-redirect=true";
}
/**
* Navigation vers la gestion des utilisateurs
*
* @return Outcome de navigation
*/
public String goToUsers() {
if (!authBean.isAuthenticated()) {
return authBean.login();
}
if (!authBean.isAdmin() && !authBean.isManager()) {
logger.warn("Accès refusé à la gestion des utilisateurs pour: {}", authBean.getUsername());
return "/pages/access-denied?faces-redirect=true";
}
currentPage = "users";
logger.debug("Navigation vers la gestion des utilisateurs");
return "/pages/users?faces-redirect=true";
}
/**
* Navigation vers la gestion des clients
*
* @return Outcome de navigation
*/
public String goToClients() {
if (!authBean.isAuthenticated()) {
return authBean.login();
}
currentPage = "clients";
logger.debug("Navigation vers la gestion des clients");
return "/pages/clients?faces-redirect=true";
}
/**
* Navigation vers la gestion des coaches
*
* @return Outcome de navigation
*/
public String goToCoaches() {
if (!authBean.isAuthenticated()) {
return authBean.login();
}
currentPage = "coaches";
logger.debug("Navigation vers la gestion des coaches");
return "/pages/coaches?faces-redirect=true";
}
/**
* Navigation vers la gestion des ateliers
*
* @return Outcome de navigation
*/
public String goToWorkshops() {
if (!authBean.isAuthenticated()) {
return authBean.login();
}
currentPage = "workshops";
logger.debug("Navigation vers la gestion des ateliers");
return "/pages/workshops?faces-redirect=true";
}
/**
* Navigation vers les sessions de coaching
*
* @return Outcome de navigation
*/
public String goToSessions() {
if (!authBean.isAuthenticated()) {
return authBean.login();
}
currentPage = "sessions";
logger.debug("Navigation vers les sessions de coaching");
return "/pages/sessions?faces-redirect=true";
}
/**
* Navigation vers le profil utilisateur
*
* @return Outcome de navigation
*/
public String goToProfile() {
if (!authBean.isAuthenticated()) {
return authBean.login();
}
currentPage = "profile";
logger.debug("Navigation vers le profil utilisateur");
return "/pages/profile?faces-redirect=true";
}
/**
* Navigation vers les paramètres (admin seulement)
*
* @return Outcome de navigation
*/
public String goToSettings() {
if (!authBean.isAuthenticated()) {
return authBean.login();
}
if (!authBean.isAdmin()) {
logger.warn("Accès refusé aux paramètres pour: {}", authBean.getUsername());
return "/pages/access-denied?faces-redirect=true";
}
currentPage = "settings";
logger.debug("Navigation vers les paramètres");
return "/pages/settings?faces-redirect=true";
}
/**
* Vérifie si une page est active
*
* @param page Nom de la page
* @return true si la page est active
*/
public boolean isPageActive(String page) {
return page.equals(currentPage);
}
/**
* Récupère la classe CSS pour un élément de menu
*
* @param page Nom de la page
* @return Classe CSS
*/
public String getMenuItemClass(String page) {
return isPageActive(page) ? "ui-state-active" : "";
}
/**
* Récupère le titre de la page courante
*
* @return Titre de la page
*/
public String getPageTitle() {
switch (currentPage) {
case "dashboard":
return "Tableau de Bord";
case "users":
return "Gestion des Utilisateurs";
case "clients":
return "Gestion des Clients";
case "coaches":
return "Gestion des Coaches";
case "workshops":
return "Gestion des Ateliers";
case "sessions":
return "Sessions de Coaching";
case "profile":
return "Mon Profil";
case "settings":
return "Paramètres";
default:
return "GBCM";
}
}
/**
* Récupère l'icône de la page courante
*
* @return Classe d'icône FontAwesome
*/
public String getPageIcon() {
switch (currentPage) {
case "dashboard":
return "pi pi-chart-line";
case "users":
return "pi pi-users";
case "clients":
return "pi pi-briefcase";
case "coaches":
return "pi pi-user-plus";
case "workshops":
return "pi pi-calendar";
case "sessions":
return "pi pi-comments";
case "profile":
return "pi pi-user";
case "settings":
return "pi pi-cog";
default:
return "pi pi-home";
}
}
/**
* Vérifie si l'utilisateur peut accéder à une page
*
* @param page Nom de la page
* @return true si l'accès est autorisé
*/
public boolean canAccessPage(String page) {
if (!authBean.isAuthenticated()) {
return false;
}
switch (page) {
case "users":
case "settings":
return authBean.isAdmin() || authBean.isManager();
case "dashboard":
case "clients":
case "coaches":
case "workshops":
case "sessions":
case "profile":
return true;
default:
return false;
}
}
// Getters et Setters
public String getCurrentPage() {
return currentPage;
}
public void setCurrentPage(String currentPage) {
this.currentPage = currentPage;
}
}

View File

@@ -1,238 +0,0 @@
package com.gbcm.client.beans.auth;
import io.quarkus.oidc.IdToken;
import io.quarkus.oidc.OidcSession;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.SessionScoped;
import jakarta.faces.application.FacesMessage;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.Set;
/**
* Bean JSF pour la gestion de l'authentification OIDC avec Keycloak
*
* @author GBCM Team
* @version 1.0.0
* @since 2025-01-01
*/
@Named("authBean")
@SessionScoped
public class AuthBean implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(AuthBean.class);
@Inject
SecurityIdentity securityIdentity;
@Inject
OidcSession oidcSession;
@Inject
@IdToken
JsonWebToken idToken;
/**
* Vérifie si l'utilisateur est authentifié
*
* @return true si authentifié
*/
public boolean isAuthenticated() {
boolean authenticated = securityIdentity != null && !securityIdentity.isAnonymous();
logger.debug("Vérification authentification: {}", authenticated);
return authenticated;
}
/**
* Récupère le nom de l'utilisateur connecté
*
* @return Nom d'utilisateur ou null
*/
public String getUsername() {
if (isAuthenticated()) {
String username = securityIdentity.getPrincipal().getName();
logger.debug("Nom d'utilisateur: {}", username);
return username;
}
return null;
}
/**
* Récupère l'email de l'utilisateur connecté
*
* @return Email ou null
*/
public String getEmail() {
if (isAuthenticated() && idToken != null) {
String email = idToken.getClaim("email");
logger.debug("Email utilisateur: {}", email);
return email;
}
return getUsername(); // Fallback sur le username
}
/**
* Récupère le nom complet de l'utilisateur
*
* @return Nom complet ou null
*/
public String getFullName() {
if (isAuthenticated() && idToken != null) {
String firstName = idToken.getClaim("given_name");
String lastName = idToken.getClaim("family_name");
if (firstName != null && lastName != null) {
return firstName + " " + lastName;
} else if (firstName != null) {
return firstName;
} else if (lastName != null) {
return lastName;
}
}
return getUsername(); // Fallback sur le username
}
/**
* Récupère les rôles de l'utilisateur
*
* @return Set des rôles
*/
public Set<String> getRoles() {
if (isAuthenticated()) {
Set<String> roles = securityIdentity.getRoles();
logger.debug("Rôles utilisateur: {}", roles);
return roles;
}
return Set.of();
}
/**
* Vérifie si l'utilisateur a un rôle spécifique
*
* @param role Rôle à vérifier
* @return true si l'utilisateur a le rôle
*/
public boolean hasRole(String role) {
boolean hasRole = isAuthenticated() && securityIdentity.hasRole(role);
logger.debug("Vérification rôle '{}': {}", role, hasRole);
return hasRole;
}
/**
* Vérifie si l'utilisateur est administrateur
*
* @return true si admin
*/
public boolean isAdmin() {
return hasRole("ADMIN");
}
/**
* Vérifie si l'utilisateur est manager
*
* @return true si manager
*/
public boolean isManager() {
return hasRole("MANAGER");
}
/**
* Vérifie si l'utilisateur est coach
*
* @return true si coach
*/
public boolean isCoach() {
return hasRole("COACH");
}
/**
* Vérifie si l'utilisateur est client
*
* @return true si client
*/
public boolean isClient() {
return hasRole("CLIENT");
}
/**
* Vérifie si l'utilisateur est prospect
*
* @return true si prospect
*/
public boolean isProspect() {
return hasRole("PROSPECT");
}
/**
* Initie la connexion OIDC
*
* @return Navigation vers la page de login Keycloak
*/
public String login() {
logger.info("Initiation de la connexion OIDC");
// Quarkus OIDC redirigera automatiquement vers Keycloak
return "/gbcm/login?faces-redirect=true";
}
/**
* Déconnexion OIDC
*
* @return Navigation vers la page d'accueil
*/
public String logout() {
try {
logger.info("Déconnexion OIDC pour l'utilisateur: {}", getUsername());
if (oidcSession != null) {
oidcSession.logout().await().indefinitely();
}
// Invalider la session JSF
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
// Message de confirmation
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_INFO, "Déconnexion réussie",
"Vous avez été déconnecté avec succès."));
logger.info("Déconnexion OIDC terminée");
return "/index?faces-redirect=true";
} catch (Exception e) {
logger.error("Erreur lors de la déconnexion OIDC", e);
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, "Erreur de déconnexion",
"Une erreur s'est produite lors de la déconnexion."));
return null;
}
}
/**
* Récupère l'URL de déconnexion Keycloak
*
* @return URL de déconnexion
*/
public String getLogoutUrl() {
return "http://localhost:8180/realms/gbcm-llc/protocol/openid-connect/logout" +
"?redirect_uri=http://localhost:8081/gbcm/";
}
/**
* Récupère des informations sur le token ID
*
* @return Informations du token ou message d'erreur
*/
public String getTokenInfo() {
if (idToken != null) {
return "Token émis par: " + idToken.getIssuer() +
", Expire à: " + idToken.getExpirationTime();
}
return "Aucun token ID disponible";
}
}

View File

@@ -0,0 +1,76 @@
package com.gbcm.client.resources;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.oidc.OidcSession;
import io.quarkus.oidc.IdToken;
import org.eclipse.microprofile.jwt.JsonWebToken;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/")
public class HomeController {
@Inject
Template index;
@Inject
SecurityIdentity securityIdentity;
@Inject
OidcSession oidcSession;
@Inject
@IdToken
JsonWebToken idToken;
@GET
@Produces(MediaType.TEXT_HTML)
public TemplateInstance index() {
return index
.data("user", getCurrentUser())
.data("authenticated", isAuthenticated())
.data("title", "Accueil - GBCM");
}
@GET
@Path("/index")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance indexPage() {
return index();
}
private boolean isAuthenticated() {
return securityIdentity != null && !securityIdentity.isAnonymous();
}
private UserInfo getCurrentUser() {
if (!isAuthenticated()) {
return new UserInfo("Invité", "guest@example.com", "GUEST");
}
String username = securityIdentity.getPrincipal().getName();
String email = idToken != null ? idToken.getClaim("email") : username;
String role = securityIdentity.getRoles().isEmpty() ? "USER" :
securityIdentity.getRoles().iterator().next();
return new UserInfo(username, email, role);
}
public static class UserInfo {
public final String name;
public final String email;
public final String role;
public UserInfo(String name, String email, String role) {
this.name = name;
this.email = email;
this.role = role;
}
}
}

View File

@@ -0,0 +1,111 @@
package com.gbcm.client.resources;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.oidc.OidcSession;
import io.quarkus.oidc.IdToken;
import org.eclipse.microprofile.jwt.JsonWebToken;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/pages")
public class PageController {
@Inject
Template dashboard;
@Inject
Template clients;
@Inject
Template profile;
@Inject
SecurityIdentity securityIdentity;
@Inject
OidcSession oidcSession;
@Inject
@IdToken
JsonWebToken idToken;
@GET
@Path("/dashboard")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance dashboard() {
return dashboard
.data("user", getCurrentUser())
.data("authenticated", isAuthenticated())
.data("title", "Tableau de Bord - GBCM");
}
@GET
@Path("/clients")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance clients() {
return clients
.data("user", getCurrentUser())
.data("authenticated", isAuthenticated())
.data("title", "Gestion des Clients - GBCM");
}
@GET
@Path("/profile")
@Produces(MediaType.TEXT_HTML)
public TemplateInstance profile() {
return profile
.data("user", getCurrentUser())
.data("authenticated", isAuthenticated())
.data("title", "Profil Utilisateur - GBCM")
.data("tokenInfo", getTokenInfo());
}
private boolean isAuthenticated() {
return securityIdentity != null && !securityIdentity.isAnonymous();
}
private UserInfo getCurrentUser() {
if (!isAuthenticated()) {
return new UserInfo("Invité", "guest@example.com", "GUEST");
}
String username = securityIdentity.getPrincipal().getName();
String email = idToken != null ? idToken.getClaim("email") : username;
String role = securityIdentity.getRoles().isEmpty() ? "USER" :
securityIdentity.getRoles().iterator().next();
return new UserInfo(username, email, role);
}
private String getTokenInfo() {
if (idToken == null) {
return "Aucun token disponible";
}
StringBuilder info = new StringBuilder();
info.append("Issuer: ").append(idToken.getIssuer()).append("\n");
info.append("Subject: ").append(idToken.getSubject()).append("\n");
info.append("Expiration: ").append(idToken.getExpirationTime()).append("\n");
info.append("Issued At: ").append(idToken.getIssuedAtTime()).append("\n");
return info.toString();
}
public static class UserInfo {
public final String name;
public final String email;
public final String role;
public UserInfo(String name, String email, String role) {
this.name = name;
this.email = email;
this.role = role;
}
}
}

View File

@@ -1,42 +0,0 @@
package com.gbcm.client.web;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import io.quarkus.qute.Template;
import io.quarkus.qute.TemplateInstance;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Contrôleur principal pour l'interface web GBCM
*
* @author GBCM Team
* @version 1.0.0
* @since 2025-01-01
*/
@Path("/")
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
@Inject
Template index;
@GET
@Produces(MediaType.TEXT_HTML)
public TemplateInstance home() {
logger.info("Accès à la page d'accueil GBCM");
return index.data("title", "GBCM - Global Business Consulting & Management")
.data("message", "Bienvenue dans l'interface web GBCM");
}
@GET
@Path("/health")
@Produces(MediaType.APPLICATION_JSON)
public String health() {
return "{\"status\":\"UP\",\"service\":\"gbcm-client-web\"}";
}
}

View File

@@ -0,0 +1,329 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.sidebar {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.client-card {
transition: transform 0.2s;
}
.client-card:hover {
transform: translateY(-5px);
}
.status-badge {
font-size: 0.75rem;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
<div class="position-sticky pt-3">
<div class="text-center mb-4">
<h4 class="text-white">GBCM</h4>
<p class="text-white-50">Global Business Consulting</p>
</div>
{#if authenticated}
<div class="text-center mb-4">
<div class="bg-white rounded-circle d-inline-flex align-items-center justify-content-center"
style="width: 50px; height: 50px;">
<i class="fas fa-user text-primary"></i>
</div>
<div class="text-white mt-2">
<div class="fw-bold">{user.name}</div>
<small class="text-white-50">{user.email}</small>
</div>
</div>
{/if}
<ul class="nav nav-pills flex-column mb-auto">
<li class="nav-item">
<a href="/gbcm/pages/dashboard" class="nav-link text-white">
<i class="fas fa-chart-line me-2"></i>
Tableau de Bord
</a>
</li>
<li class="nav-item">
<a href="/gbcm/pages/clients" class="nav-link text-white active">
<i class="fas fa-briefcase me-2"></i>
Clients
</a>
</li>
<li class="nav-item">
<a href="/gbcm/pages/profile" class="nav-link text-white">
<i class="fas fa-user me-2"></i>
Profil
</a>
</li>
</ul>
</div>
</nav>
<!-- Main content -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Gestion des Clients</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-primary">
<i class="fas fa-plus me-1"></i>
Nouveau Client
</button>
<button type="button" class="btn btn-outline-secondary">
<i class="fas fa-download me-1"></i>
Exporter
</button>
</div>
</div>
</div>
<!-- Search and Filters -->
<div class="row mb-4">
<div class="col-md-6">
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search"></i></span>
<input type="text" class="form-control" placeholder="Rechercher un client...">
</div>
</div>
<div class="col-md-3">
<select class="form-select">
<option selected>Tous les statuts</option>
<option value="actif">Actif</option>
<option value="inactif">Inactif</option>
<option value="prospect">Prospect</option>
</select>
</div>
<div class="col-md-3">
<select class="form-select">
<option selected>Tous les secteurs</option>
<option value="tech">Technologie</option>
<option value="finance">Finance</option>
<option value="retail">Commerce</option>
</select>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-md-3">
<div class="card text-center border-primary">
<div class="card-body">
<h5 class="card-title text-primary">156</h5>
<p class="card-text">Clients Actifs</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center border-success">
<div class="card-body">
<h5 class="card-title text-success">23</h5>
<p class="card-text">Nouveaux ce mois</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center border-warning">
<div class="card-body">
<h5 class="card-title text-warning">12</h5>
<p class="card-text">Prospects</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center border-info">
<div class="card-body">
<h5 class="card-title text-info">€2.4M</h5>
<p class="card-text">Chiffre d'affaires</p>
</div>
</div>
</div>
</div>
<!-- Clients List -->
<div class="card shadow">
<div class="card-header">
<h6 class="m-0 font-weight-bold text-primary">Liste des Clients</h6>
</div>
<div class="card-body">
<div class="row">
<!-- Client Card 1 -->
<div class="col-lg-4 col-md-6 mb-4">
<div class="card client-card h-100 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div class="d-flex align-items-center">
<div class="bg-primary rounded-circle d-flex align-items-center justify-content-center me-3"
style="width: 40px; height: 40px;">
<span class="text-white fw-bold">TC</span>
</div>
<div>
<h6 class="mb-0">TechCorp Solutions</h6>
<small class="text-muted">Technologie</small>
</div>
</div>
<span class="badge bg-success status-badge">Actif</span>
</div>
<p class="card-text text-muted small">
Société de développement logiciel spécialisée dans les solutions cloud.
</p>
<div class="row text-center">
<div class="col-4">
<small class="text-muted">Projets</small>
<div class="fw-bold">8</div>
</div>
<div class="col-4">
<small class="text-muted">CA</small>
<div class="fw-bold">€450K</div>
</div>
<div class="col-4">
<small class="text-muted">Depuis</small>
<div class="fw-bold">2022</div>
</div>
</div>
<div class="mt-3">
<button class="btn btn-sm btn-outline-primary me-2">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-sm btn-outline-secondary me-2">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-outline-info">
<i class="fas fa-envelope"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Client Card 2 -->
<div class="col-lg-4 col-md-6 mb-4">
<div class="card client-card h-100 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div class="d-flex align-items-center">
<div class="bg-success rounded-circle d-flex align-items-center justify-content-center me-3"
style="width: 40px; height: 40px;">
<span class="text-white fw-bold">GT</span>
</div>
<div>
<h6 class="mb-0">GlobalTech Inc.</h6>
<small class="text-muted">Finance</small>
</div>
</div>
<span class="badge bg-success status-badge">Actif</span>
</div>
<p class="card-text text-muted small">
Entreprise fintech proposant des solutions de paiement innovantes.
</p>
<div class="row text-center">
<div class="col-4">
<small class="text-muted">Projets</small>
<div class="fw-bold">12</div>
</div>
<div class="col-4">
<small class="text-muted">CA</small>
<div class="fw-bold">€780K</div>
</div>
<div class="col-4">
<small class="text-muted">Depuis</small>
<div class="fw-bold">2021</div>
</div>
</div>
<div class="mt-3">
<button class="btn btn-sm btn-outline-primary me-2">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-sm btn-outline-secondary me-2">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-outline-info">
<i class="fas fa-envelope"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Client Card 3 -->
<div class="col-lg-4 col-md-6 mb-4">
<div class="card client-card h-100 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-3">
<div class="d-flex align-items-center">
<div class="bg-warning rounded-circle d-flex align-items-center justify-content-center me-3"
style="width: 40px; height: 40px;">
<span class="text-white fw-bold">RS</span>
</div>
<div>
<h6 class="mb-0">RetailSmart</h6>
<small class="text-muted">Commerce</small>
</div>
</div>
<span class="badge bg-warning status-badge">Prospect</span>
</div>
<p class="card-text text-muted small">
Chaîne de magasins cherchant à digitaliser ses processus.
</p>
<div class="row text-center">
<div class="col-4">
<small class="text-muted">Projets</small>
<div class="fw-bold">2</div>
</div>
<div class="col-4">
<small class="text-muted">CA</small>
<div class="fw-bold">€120K</div>
</div>
<div class="col-4">
<small class="text-muted">Depuis</small>
<div class="fw-bold">2024</div>
</div>
</div>
<div class="mt-3">
<button class="btn btn-sm btn-outline-primary me-2">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-sm btn-outline-secondary me-2">
<i class="fas fa-edit"></i>
</button>
<button class="btn btn-sm btn-outline-info">
<i class="fas fa-envelope"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
<li class="page-item disabled">
<a class="page-link" href="#" tabindex="-1">Précédent</a>
</li>
<li class="page-item active"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item">
<a class="page-link" href="#">Suivant</a>
</li>
</ul>
</nav>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,252 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.sidebar {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card-stats {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.card-stats-2 {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
color: white;
}
.card-stats-3 {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
color: white;
}
.card-stats-4 {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
color: white;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
<div class="position-sticky pt-3">
<div class="text-center mb-4">
<h4 class="text-white">GBCM</h4>
<p class="text-white-50">Global Business Consulting</p>
</div>
{#if authenticated}
<div class="text-center mb-4">
<div class="bg-white rounded-circle d-inline-flex align-items-center justify-content-center"
style="width: 50px; height: 50px;">
<i class="fas fa-user text-primary"></i>
</div>
<div class="text-white mt-2">
<div class="fw-bold">{user.name}</div>
<small class="text-white-50">{user.email}</small>
</div>
</div>
{/if}
<ul class="nav nav-pills flex-column mb-auto">
<li class="nav-item">
<a href="/gbcm/pages/dashboard" class="nav-link text-white active">
<i class="fas fa-chart-line me-2"></i>
Tableau de Bord
</a>
</li>
<li class="nav-item">
<a href="/gbcm/pages/clients" class="nav-link text-white">
<i class="fas fa-briefcase me-2"></i>
Clients
</a>
</li>
<li class="nav-item">
<a href="/gbcm/pages/profile" class="nav-link text-white">
<i class="fas fa-user me-2"></i>
Profil
</a>
</li>
</ul>
</div>
</nav>
<!-- Main content -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Tableau de Bord</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-download me-1"></i>
Exporter
</button>
</div>
</div>
</div>
<!-- Statistics Cards -->
<div class="row mb-4">
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stats border-0 shadow">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase mb-0">Clients Actifs</h5>
<span class="h2 font-weight-bold mb-0">156</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-primary rounded-circle shadow">
<i class="fas fa-users fa-2x"></i>
</div>
</div>
</div>
<p class="mt-3 mb-0 text-white-50 text-sm">
<span class="text-success mr-2"><i class="fa fa-arrow-up"></i> 3.48%</span>
<span class="text-nowrap">Depuis le mois dernier</span>
</p>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stats-2 border-0 shadow">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase mb-0">Revenus</h5>
<span class="h2 font-weight-bold mb-0">€2.4M</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-primary rounded-circle shadow">
<i class="fas fa-euro-sign fa-2x"></i>
</div>
</div>
</div>
<p class="mt-3 mb-0 text-white-50 text-sm">
<span class="text-success mr-2"><i class="fa fa-arrow-up"></i> 5.4%</span>
<span class="text-nowrap">Depuis le mois dernier</span>
</p>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stats-3 border-0 shadow">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase mb-0">Projets</h5>
<span class="h2 font-weight-bold mb-0">49</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-primary rounded-circle shadow">
<i class="fas fa-project-diagram fa-2x"></i>
</div>
</div>
</div>
<p class="mt-3 mb-0 text-white-50 text-sm">
<span class="text-success mr-2"><i class="fa fa-arrow-up"></i> 1.10%</span>
<span class="text-nowrap">Depuis le mois dernier</span>
</p>
</div>
</div>
</div>
<div class="col-xl-3 col-md-6 mb-4">
<div class="card card-stats-4 border-0 shadow">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase mb-0">Satisfaction</h5>
<span class="h2 font-weight-bold mb-0">94%</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-primary rounded-circle shadow">
<i class="fas fa-heart fa-2x"></i>
</div>
</div>
</div>
<p class="mt-3 mb-0 text-white-50 text-sm">
<span class="text-success mr-2"><i class="fa fa-arrow-up"></i> 12%</span>
<span class="text-nowrap">Depuis le mois dernier</span>
</p>
</div>
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="row">
<div class="col-lg-8">
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Activité Récente</h6>
</div>
<div class="card-body">
<div class="timeline">
<div class="timeline-item">
<div class="timeline-marker bg-primary"></div>
<div class="timeline-content">
<h6 class="timeline-title">Nouveau client ajouté</h6>
<p class="timeline-text">TechCorp Solutions a été ajouté comme nouveau client.</p>
<small class="text-muted">Il y a 2 heures</small>
</div>
</div>
<div class="timeline-item">
<div class="timeline-marker bg-success"></div>
<div class="timeline-content">
<h6 class="timeline-title">Projet terminé</h6>
<p class="timeline-text">Le projet "Migration Cloud" a été terminé avec succès.</p>
<small class="text-muted">Il y a 5 heures</small>
</div>
</div>
<div class="timeline-item">
<div class="timeline-marker bg-warning"></div>
<div class="timeline-content">
<h6 class="timeline-title">Réunion programmée</h6>
<p class="timeline-text">Réunion avec GlobalTech prévue pour demain à 14h.</p>
<small class="text-muted">Il y a 1 jour</small>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Actions Rapides</h6>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button class="btn btn-primary" type="button">
<i class="fas fa-plus me-2"></i>
Nouveau Client
</button>
<button class="btn btn-success" type="button">
<i class="fas fa-project-diagram me-2"></i>
Nouveau Projet
</button>
<button class="btn btn-info" type="button">
<i class="fas fa-calendar me-2"></i>
Planifier Réunion
</button>
<button class="btn btn-warning" type="button">
<i class="fas fa-file-alt me-2"></i>
Générer Rapport
</button>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,298 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.sidebar {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.profile-avatar {
width: 120px;
height: 120px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.info-card {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.token-info {
background-color: #f8f9fa;
border-left: 4px solid #007bff;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<nav class="col-md-3 col-lg-2 d-md-block sidebar collapse">
<div class="position-sticky pt-3">
<div class="text-center mb-4">
<h4 class="text-white">GBCM</h4>
<p class="text-white-50">Global Business Consulting</p>
</div>
{#if authenticated}
<div class="text-center mb-4">
<div class="bg-white rounded-circle d-inline-flex align-items-center justify-content-center"
style="width: 50px; height: 50px;">
<i class="fas fa-user text-primary"></i>
</div>
<div class="text-white mt-2">
<div class="fw-bold">{user.name}</div>
<small class="text-white-50">{user.email}</small>
</div>
</div>
{/if}
<ul class="nav nav-pills flex-column mb-auto">
<li class="nav-item">
<a href="/gbcm/pages/dashboard" class="nav-link text-white">
<i class="fas fa-chart-line me-2"></i>
Tableau de Bord
</a>
</li>
<li class="nav-item">
<a href="/gbcm/pages/clients" class="nav-link text-white">
<i class="fas fa-briefcase me-2"></i>
Clients
</a>
</li>
<li class="nav-item">
<a href="/gbcm/pages/profile" class="nav-link text-white active">
<i class="fas fa-user me-2"></i>
Profil
</a>
</li>
</ul>
</div>
</nav>
<!-- Main content -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Profil Utilisateur</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-outline-primary">
<i class="fas fa-edit me-1"></i>
Modifier
</button>
<button type="button" class="btn btn-outline-secondary">
<i class="fas fa-key me-1"></i>
Changer mot de passe
</button>
</div>
</div>
</div>
<div class="row">
<!-- Profile Information -->
<div class="col-lg-4">
<div class="card shadow mb-4">
<div class="card-body text-center">
<div class="profile-avatar rounded-circle mx-auto d-flex align-items-center justify-content-center mb-3">
<i class="fas fa-user fa-3x text-white"></i>
</div>
<h5 class="card-title">{user.name}</h5>
<p class="text-muted">{user.email}</p>
<span class="badge bg-primary fs-6">{user.role}</span>
<hr>
<div class="row text-center">
<div class="col-4">
<div class="fw-bold text-primary">156</div>
<small class="text-muted">Clients</small>
</div>
<div class="col-4">
<div class="fw-bold text-success">49</div>
<small class="text-muted">Projets</small>
</div>
<div class="col-4">
<div class="fw-bold text-warning">12</div>
<small class="text-muted">Tâches</small>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="card shadow">
<div class="card-header">
<h6 class="m-0 font-weight-bold text-primary">Actions Rapides</h6>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<button class="btn btn-outline-primary" type="button">
<i class="fas fa-download me-2"></i>
Télécharger mes données
</button>
<button class="btn btn-outline-success" type="button">
<i class="fas fa-file-export me-2"></i>
Exporter rapport
</button>
<button class="btn btn-outline-info" type="button">
<i class="fas fa-bell me-2"></i>
Notifications
</button>
<button class="btn btn-outline-danger" type="button">
<i class="fas fa-sign-out-alt me-2"></i>
Déconnexion
</button>
</div>
</div>
</div>
</div>
<!-- Profile Details -->
<div class="col-lg-8">
<!-- Personal Information -->
<div class="card shadow mb-4">
<div class="card-header">
<h6 class="m-0 font-weight-bold text-primary">Informations Personnelles</h6>
</div>
<div class="card-body">
<form>
<div class="row">
<div class="col-md-6 mb-3">
<label for="firstName" class="form-label">Prénom</label>
<input type="text" class="form-control" id="firstName" value="{user.name}">
</div>
<div class="col-md-6 mb-3">
<label for="lastName" class="form-label">Nom</label>
<input type="text" class="form-control" id="lastName" value="Utilisateur">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" value="{user.email}">
</div>
<div class="col-md-6 mb-3">
<label for="phone" class="form-label">Téléphone</label>
<input type="tel" class="form-control" id="phone" value="+33 1 23 45 67 89">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="department" class="form-label">Département</label>
<select class="form-select" id="department">
<option selected>Consulting</option>
<option>Management</option>
<option>Administration</option>
<option>Commercial</option>
</select>
</div>
<div class="col-md-6 mb-3">
<label for="role" class="form-label">Rôle</label>
<input type="text" class="form-control" id="role" value="{user.role}" readonly>
</div>
</div>
<div class="mb-3">
<label for="address" class="form-label">Adresse</label>
<textarea class="form-control" id="address" rows="2">123 Rue de la Paix, 75001 Paris, France</textarea>
</div>
</form>
</div>
</div>
<!-- Authentication Information -->
{#if authenticated}
<div class="card shadow mb-4">
<div class="card-header">
<h6 class="m-0 font-weight-bold text-primary">Informations d'Authentification</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h6 class="text-muted">Statut de connexion</h6>
<span class="badge bg-success fs-6">
<i class="fas fa-check-circle me-1"></i>
Connecté
</span>
</div>
<div class="col-md-6">
<h6 class="text-muted">Dernière connexion</h6>
<p class="mb-0">Aujourd'hui à 14:30</p>
</div>
</div>
<hr>
<div class="token-info p-3 rounded">
<h6 class="text-primary mb-3">
<i class="fas fa-key me-2"></i>
Informations du Token OIDC
</h6>
<pre class="mb-0 small text-dark">{tokenInfo}</pre>
</div>
</div>
</div>
{/if}
<!-- Activity Log -->
<div class="card shadow">
<div class="card-header">
<h6 class="m-0 font-weight-bold text-primary">Activité Récente</h6>
</div>
<div class="card-body">
<div class="timeline">
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<div class="bg-primary rounded-circle d-flex align-items-center justify-content-center"
style="width: 32px; height: 32px;">
<i class="fas fa-sign-in-alt text-white small"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Connexion réussie</h6>
<p class="text-muted small mb-0">Connexion depuis l'adresse IP 192.168.1.100</p>
<small class="text-muted">Il y a 2 heures</small>
</div>
</div>
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<div class="bg-success rounded-circle d-flex align-items-center justify-content-center"
style="width: 32px; height: 32px;">
<i class="fas fa-edit text-white small"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Profil mis à jour</h6>
<p class="text-muted small mb-0">Modification des informations de contact</p>
<small class="text-muted">Il y a 1 jour</small>
</div>
</div>
<div class="d-flex mb-3">
<div class="flex-shrink-0">
<div class="bg-info rounded-circle d-flex align-items-center justify-content-center"
style="width: 32px; height: 32px;">
<i class="fas fa-file text-white small"></i>
</div>
</div>
<div class="flex-grow-1 ms-3">
<h6 class="mb-1">Rapport généré</h6>
<p class="text-muted small mb-0">Rapport mensuel des activités clients</p>
<small class="text-muted">Il y a 3 jours</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-facesconfig_4_0.xsd"
version="4.0">
<application>
<message-bundle>messages</message-bundle>
<locale-config>
<default-locale>fr</default-locale>
<supported-locale>fr</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
<!-- Navigation Rules -->
<navigation-rule>
<from-view-id>*</from-view-id>
<navigation-case>
<from-outcome>dashboard</from-outcome>
<to-view-id>/pages/dashboard.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>profile</from-outcome>
<to-view-id>/pages/profile.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>clients</from-outcome>
<to-view-id>/pages/clients.xhtml</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>home</from-outcome>
<to-view-id>/index.xhtml</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
</application>
</faces-config>

View File

@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version="5.0">
<display-name>GBCM Client Application</display-name>
<description>Interface web GBCM pour la gestion des services de consulting</description>
<!-- JSF Configuration -->
<context-param>
<param-name>jakarta.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<context-param>
<param-name>jakarta.faces.STATE_SAVING_METHOD</param-name>
<param-value>server</param-value>
</context-param>
<context-param>
<param-name>jakarta.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<!-- PrimeFaces Configuration -->
<context-param>
<param-name>primefaces.THEME</param-name>
<param-value>freya</param-value>
</context-param>
<context-param>
<param-name>primefaces.FONT_AWESOME</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>primefaces.CLIENT_SIDE_VALIDATION</param-name>
<param-value>true</param-value>
</context-param>
<!-- JSF Servlet -->
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<!-- Security Configuration - Handled by Quarkus OIDC -->
<!-- Security constraints removed - handled by Quarkus OIDC and AuthBean -->
<!-- Welcome Files -->
<welcome-file-list>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
<!-- Error Pages -->
<error-page>
<error-code>404</error-code>
<location>/pages/error/404.xhtml</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/pages/error/500.xhtml</location>
</error-page>
</web-app>