Migration complète vers PrimeFaces Freya - Corrections des incompatibilités et intégration de primefaces-freya-extension
This commit is contained in:
@@ -18,7 +18,9 @@
|
||||
<navigation-rule>
|
||||
<from-view-id>*</from-view-id>
|
||||
|
||||
<!-- Dashboard -->
|
||||
<!-- ================================================================
|
||||
DASHBOARD & ACCUEIL
|
||||
================================================================ -->
|
||||
<navigation-case>
|
||||
<description>Page d'accueil / Dashboard</description>
|
||||
<from-outcome>userManagerDashboardPage</from-outcome>
|
||||
@@ -26,7 +28,16 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- Users -->
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers dashboard</description>
|
||||
<from-outcome>/pages/user-manager/dashboard</from-outcome>
|
||||
<to-view-id>/pages/user-manager/dashboard.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- ================================================================
|
||||
GESTION DES UTILISATEURS
|
||||
================================================================ -->
|
||||
<navigation-case>
|
||||
<description>Page de liste des utilisateurs</description>
|
||||
<from-outcome>userListPage</from-outcome>
|
||||
@@ -34,6 +45,13 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers liste utilisateurs</description>
|
||||
<from-outcome>/pages/user-manager/users/list</from-outcome>
|
||||
<to-view-id>/pages/user-manager/users/list.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Page de création d'utilisateur</description>
|
||||
<from-outcome>userCreatePage</from-outcome>
|
||||
@@ -41,6 +59,13 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers création utilisateur</description>
|
||||
<from-outcome>/pages/user-manager/users/create</from-outcome>
|
||||
<to-view-id>/pages/user-manager/users/create.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Page de profil utilisateur</description>
|
||||
<from-outcome>userProfilePage</from-outcome>
|
||||
@@ -48,6 +73,27 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers profil utilisateur</description>
|
||||
<from-outcome>/pages/user-manager/users/profile</from-outcome>
|
||||
<to-view-id>/pages/user-manager/users/profile.xhtml</to-view-id>
|
||||
<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>
|
||||
@@ -55,7 +101,16 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- Roles -->
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers édition utilisateur</description>
|
||||
<from-outcome>/pages/user-manager/users/edit</from-outcome>
|
||||
<to-view-id>/pages/user-manager/users/edit.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- ================================================================
|
||||
GESTION DES RÔLES
|
||||
================================================================ -->
|
||||
<navigation-case>
|
||||
<description>Page de liste des rôles</description>
|
||||
<from-outcome>roleListPage</from-outcome>
|
||||
@@ -63,6 +118,13 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers liste rôles</description>
|
||||
<from-outcome>/pages/user-manager/roles/list</from-outcome>
|
||||
<to-view-id>/pages/user-manager/roles/list.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Page d'attribution de rôles</description>
|
||||
<from-outcome>roleAssignPage</from-outcome>
|
||||
@@ -70,7 +132,16 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- Audit -->
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers attribution rôles</description>
|
||||
<from-outcome>/pages/user-manager/roles/assign</from-outcome>
|
||||
<to-view-id>/pages/user-manager/roles/assign.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- ================================================================
|
||||
AUDIT
|
||||
================================================================ -->
|
||||
<navigation-case>
|
||||
<description>Page de journal d'audit</description>
|
||||
<from-outcome>auditLogsPage</from-outcome>
|
||||
@@ -78,7 +149,16 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- Sync -->
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers journal d'audit</description>
|
||||
<from-outcome>/pages/user-manager/audit/logs</from-outcome>
|
||||
<to-view-id>/pages/user-manager/audit/logs.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- ================================================================
|
||||
SYNCHRONISATION
|
||||
================================================================ -->
|
||||
<navigation-case>
|
||||
<description>Page de dashboard synchronisation</description>
|
||||
<from-outcome>syncDashboardPage</from-outcome>
|
||||
@@ -86,6 +166,30 @@
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers dashboard synchronisation</description>
|
||||
<from-outcome>/pages/user-manager/sync/dashboard</from-outcome>
|
||||
<to-view-id>/pages/user-manager/sync/dashboard.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<!-- ================================================================
|
||||
PARAMÈTRES & PROFIL
|
||||
================================================================ -->
|
||||
<navigation-case>
|
||||
<description>Page de paramètres utilisateur</description>
|
||||
<from-outcome>settingsPage</from-outcome>
|
||||
<to-view-id>/pages/user-manager/settings.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
<navigation-case>
|
||||
<description>Navigation directe vers paramètres</description>
|
||||
<from-outcome>/pages/user-manager/settings</from-outcome>
|
||||
<to-view-id>/pages/user-manager/settings.xhtml</to-view-id>
|
||||
<redirect />
|
||||
</navigation-case>
|
||||
|
||||
</navigation-rule>
|
||||
</faces-config>
|
||||
|
||||
|
||||
982
src/main/resources/META-INF/resources/index.html
Normal file
982
src/main/resources/META-INF/resources/index.html
Normal file
@@ -0,0 +1,982 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Lions User Manager - Plateforme de Gestion IAM Centralisée</title>
|
||||
|
||||
<!-- PrimeIcons -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/primeicons@7.0.0/primeicons.css">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
/* ============================================
|
||||
FREYA BLUE - Couleurs officielles du template
|
||||
============================================ */
|
||||
|
||||
/* Primary Blue - Freya Template */
|
||||
--primary-color: #4F8EEC;
|
||||
--primary-50: #EBF3FE;
|
||||
--primary-100: #D7E7FD;
|
||||
--primary-200: #AECFFB;
|
||||
--primary-300: #86B7F9;
|
||||
--primary-400: #5D9FF6;
|
||||
--primary-500: #4F8EEC; /* Base */
|
||||
--primary-600: #387FE9; /* Hover */
|
||||
--primary-700: #2C6DCC; /* Active */
|
||||
--primary-800: #2159A8;
|
||||
--primary-900: #164684;
|
||||
|
||||
/* Surface - Freya Neutral Colors */
|
||||
--surface-0: #ffffff;
|
||||
--surface-50: #FAFAFA;
|
||||
--surface-100: #F5F5F5;
|
||||
--surface-200: #EEEEEE;
|
||||
--surface-300: #E0E0E0;
|
||||
--surface-400: #BDBDBD;
|
||||
--surface-500: #9E9E9E;
|
||||
--surface-600: #757575;
|
||||
--surface-700: #616161;
|
||||
--surface-800: #424242;
|
||||
--surface-900: #212121;
|
||||
|
||||
/* Text Colors - Freya */
|
||||
--text-color: #495057;
|
||||
--text-color-secondary: #6c757d;
|
||||
|
||||
/* Semantic Colors - Freya */
|
||||
--blue-500: #4F8EEC;
|
||||
--green-500: #34D399;
|
||||
--red-500: #EF4444;
|
||||
--yellow-500: #F59E0B;
|
||||
|
||||
/* Border & Shadows */
|
||||
--border-radius: 12px;
|
||||
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--surface-0);
|
||||
color: var(--text-color);
|
||||
overflow-x: hidden;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* ==================== NAVBAR ==================== */
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--surface-200);
|
||||
padding: 1rem 0;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.navbar.scrolled {
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
}
|
||||
|
||||
.navbar-container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 0 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-weight: 800;
|
||||
font-size: 1.25rem;
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background: linear-gradient(135deg, var(--primary-500), var(--primary-700));
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4px 14px rgba(79, 142, 236, 0.35);
|
||||
}
|
||||
|
||||
.logo-icon i {
|
||||
color: white;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.nav-cta {
|
||||
background: linear-gradient(135deg, var(--primary-500), var(--primary-700));
|
||||
color: white;
|
||||
padding: 0.75rem 1.75rem;
|
||||
border-radius: var(--border-radius);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
box-shadow: 0 4px 14px rgba(79, 142, 236, 0.35);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.nav-cta:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(79, 142, 236, 0.45);
|
||||
background: linear-gradient(135deg, var(--primary-600), var(--primary-800));
|
||||
}
|
||||
|
||||
/* ==================== HERO SECTION ==================== */
|
||||
.hero {
|
||||
margin-top: 80px;
|
||||
padding: 6rem 2rem 4rem;
|
||||
background: linear-gradient(180deg, var(--surface-0) 0%, var(--primary-50) 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -20%;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
background: radial-gradient(circle, rgba(79, 142, 236, 0.12) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
50% { transform: translate(-30px, -30px) scale(1.1); }
|
||||
}
|
||||
|
||||
.hero-container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background: white;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 50px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--primary-600);
|
||||
box-shadow: 0 2px 12px rgba(79, 142, 236, 0.15);
|
||||
margin-bottom: 2rem;
|
||||
animation: slideDown 0.8s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from { opacity: 0; transform: translateY(-20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.hero-badge i {
|
||||
font-size: 1rem;
|
||||
color: var(--primary-500);
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 3.75rem;
|
||||
font-weight: 900;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 1.5rem;
|
||||
background: linear-gradient(135deg, var(--text-color), var(--primary-700));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
animation: fadeInUp 0.8s ease-out 0.2s both;
|
||||
}
|
||||
|
||||
@keyframes fadeInUp {
|
||||
from { opacity: 0; transform: translateY(30px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.375rem;
|
||||
color: var(--text-color-secondary);
|
||||
margin-bottom: 3rem;
|
||||
line-height: 1.7;
|
||||
font-weight: 400;
|
||||
animation: fadeInUp 0.8s ease-out 0.4s both;
|
||||
}
|
||||
|
||||
.hero-cta-group {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
animation: fadeInUp 0.8s ease-out 0.6s both;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-500), var(--primary-700));
|
||||
color: white;
|
||||
padding: 1rem 2.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
font-size: 1.125rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
box-shadow: 0 8px 24px rgba(79, 142, 236, 0.4);
|
||||
transition: var(--transition);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 12px 32px rgba(79, 142, 236, 0.5);
|
||||
background: linear-gradient(135deg, var(--primary-600), var(--primary-800));
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: var(--text-color);
|
||||
padding: 1rem 2.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
font-size: 1.125rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
border: 2px solid var(--surface-300);
|
||||
transition: var(--transition);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
border-color: var(--primary-500);
|
||||
color: var(--primary-600);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 24px rgba(79, 142, 236, 0.15);
|
||||
}
|
||||
|
||||
/* ==================== SESSION EXPIRED ALERT ==================== */
|
||||
.session-expired-alert {
|
||||
max-width: 600px;
|
||||
margin: 0 auto 3rem;
|
||||
background: linear-gradient(135deg, #FEE2E2, #FECACA);
|
||||
border: 2px solid #FCA5A5;
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.25rem 1.5rem;
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
box-shadow: 0 4px 16px rgba(239, 68, 68, 0.15);
|
||||
animation: slideDown 0.6s ease-out;
|
||||
}
|
||||
|
||||
.session-expired-alert.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.session-expired-alert i {
|
||||
color: var(--red-500);
|
||||
font-size: 2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.session-expired-alert .message {
|
||||
color: #7F1D1D;
|
||||
font-weight: 600;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ==================== STATS SECTION ==================== */
|
||||
.stats-section {
|
||||
padding: 4rem 2rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
padding: 2rem 1.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--surface-50);
|
||||
transition: var(--transition);
|
||||
border: 1px solid var(--surface-200);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--primary-200);
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 3rem;
|
||||
font-weight: 900;
|
||||
background: linear-gradient(135deg, var(--primary-500), var(--primary-700));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 1rem;
|
||||
color: var(--text-color-secondary);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* ==================== FEATURES SECTION ==================== */
|
||||
.features-section {
|
||||
padding: 6rem 2rem;
|
||||
background: linear-gradient(180deg, white 0%, var(--surface-50) 100%);
|
||||
}
|
||||
|
||||
.features-header {
|
||||
max-width: 720px;
|
||||
margin: 0 auto 4rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-badge {
|
||||
display: inline-block;
|
||||
background: var(--primary-100);
|
||||
color: var(--primary-700);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 50px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.features-header h2 {
|
||||
font-size: 3rem;
|
||||
font-weight: 900;
|
||||
margin-bottom: 1.25rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.features-header p {
|
||||
font-size: 1.25rem;
|
||||
color: var(--text-color-secondary);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 2.5rem;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
background: white;
|
||||
padding: 2.5rem;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--surface-200);
|
||||
transition: var(--transition);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.feature-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, var(--primary-500), var(--primary-700));
|
||||
transform: scaleX(0);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 20px 48px rgba(0, 0, 0, 0.12);
|
||||
border-color: var(--primary-300);
|
||||
}
|
||||
|
||||
.feature-card:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: linear-gradient(135deg, var(--primary-100), var(--primary-200));
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.feature-card:hover .feature-icon {
|
||||
background: linear-gradient(135deg, var(--primary-500), var(--primary-700));
|
||||
}
|
||||
|
||||
.feature-icon i {
|
||||
font-size: 2rem;
|
||||
color: var(--primary-600);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.feature-card:hover .feature-icon i {
|
||||
color: white;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.feature-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.feature-description {
|
||||
color: var(--text-color-secondary);
|
||||
line-height: 1.7;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
margin-top: 1.25rem;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.feature-list li i {
|
||||
color: var(--primary-500);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* ==================== CTA SECTION ==================== */
|
||||
.cta-section {
|
||||
padding: 6rem 2rem;
|
||||
background: linear-gradient(135deg, var(--primary-600), var(--primary-800));
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cta-section::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -10%;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.cta-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cta-container h2 {
|
||||
font-size: 3rem;
|
||||
font-weight: 900;
|
||||
color: white;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.cta-container p {
|
||||
font-size: 1.375rem;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
margin-bottom: 3rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.btn-cta-white {
|
||||
background: white;
|
||||
color: var(--primary-600);
|
||||
padding: 1.25rem 3rem;
|
||||
border-radius: var(--border-radius);
|
||||
text-decoration: none;
|
||||
font-weight: 800;
|
||||
font-size: 1.25rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-cta-white:hover {
|
||||
transform: translateY(-4px) scale(1.05);
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3);
|
||||
color: var(--primary-700);
|
||||
}
|
||||
|
||||
/* ==================== FOOTER ==================== */
|
||||
.footer {
|
||||
background: var(--surface-900);
|
||||
color: var(--surface-400);
|
||||
padding: 3rem 2rem 2rem;
|
||||
}
|
||||
|
||||
.footer-container {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-logo-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: linear-gradient(135deg, var(--primary-500), var(--primary-700));
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-logo-icon i {
|
||||
color: white;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.footer-logo-text {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
color: var(--surface-500);
|
||||
}
|
||||
|
||||
.footer-divider {
|
||||
height: 1px;
|
||||
background: var(--surface-800);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.footer-bottom i {
|
||||
color: var(--primary-500);
|
||||
}
|
||||
|
||||
/* ==================== RESPONSIVE ==================== */
|
||||
@media (max-width: 768px) {
|
||||
.hero {
|
||||
padding: 4rem 1.5rem 3rem;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
.hero-cta-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn-primary, .btn-secondary {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.features-header h2,
|
||||
.cta-container h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.stats-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.navbar-container {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.nav-cta {
|
||||
padding: 0.625rem 1.25rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- ==================== NAVBAR ==================== -->
|
||||
<nav class="navbar" id="navbar">
|
||||
<div class="navbar-container">
|
||||
<a href="/" class="logo">
|
||||
<div class="logo-icon">
|
||||
<i class="pi pi-users"></i>
|
||||
</div>
|
||||
<span>Lions User Manager</span>
|
||||
</a>
|
||||
<a href="/pages/user-manager/dashboard.xhtml" class="nav-cta">
|
||||
Accéder à la console
|
||||
<i class="pi pi-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- ==================== HERO SECTION ==================== -->
|
||||
<section class="hero">
|
||||
<div class="hero-container">
|
||||
<!-- Session Expired Alert -->
|
||||
<div id="sessionExpiredAlert" class="session-expired-alert">
|
||||
<i class="pi pi-exclamation-triangle"></i>
|
||||
<div class="message">
|
||||
Votre session a expiré pour des raisons de sécurité. Veuillez vous reconnecter pour accéder à la plateforme.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-content">
|
||||
<div class="hero-badge">
|
||||
<i class="pi pi-shield"></i>
|
||||
Plateforme IAM Centralisée
|
||||
</div>
|
||||
|
||||
<h1>Gérez vos utilisateurs Keycloak en toute simplicité</h1>
|
||||
|
||||
<p class="hero-subtitle">
|
||||
Une interface moderne et intuitive pour administrer vos identités, rôles et permissions à travers tous vos royaumes Keycloak. Sécurisé, performant, professionnel.
|
||||
</p>
|
||||
|
||||
<div class="hero-cta-group">
|
||||
<a href="/pages/user-manager/dashboard.xhtml" class="btn-primary">
|
||||
<i class="pi pi-sign-in"></i>
|
||||
Se connecter avec Keycloak
|
||||
</a>
|
||||
<a href="#features" class="btn-secondary">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
Découvrir les fonctionnalités
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ==================== STATS SECTION ==================== -->
|
||||
<section class="stats-section">
|
||||
<div class="stats-container">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" data-target="10000">0</div>
|
||||
<div class="stat-label">Utilisateurs gérés</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" data-target="50">0</div>
|
||||
<div class="stat-label">Royaumes actifs</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">99.9%</div>
|
||||
<div class="stat-label">Disponibilité</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number" data-target="24">0</div>
|
||||
<div class="stat-label">Support 24/7</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ==================== FEATURES SECTION ==================== -->
|
||||
<section class="features-section" id="features">
|
||||
<div class="features-header">
|
||||
<span class="section-badge">Fonctionnalités Métier</span>
|
||||
<h2>Tout ce dont vous avez besoin pour gérer vos identités</h2>
|
||||
<p>Une suite complète d'outils pour simplifier l'administration de votre infrastructure IAM.</p>
|
||||
</div>
|
||||
|
||||
<div class="features-grid">
|
||||
<!-- Feature 1 -->
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="pi pi-user-plus"></i>
|
||||
</div>
|
||||
<h3 class="feature-title">Gestion des utilisateurs</h3>
|
||||
<p class="feature-description">
|
||||
Créez, modifiez et supprimez des utilisateurs en quelques clics. Interface intuitive avec recherche avancée et filtrage en temps réel.
|
||||
</p>
|
||||
<ul class="feature-list">
|
||||
<li><i class="pi pi-check-circle"></i> Import/Export CSV massif</li>
|
||||
<li><i class="pi pi-check-circle"></i> Recherche multi-critères</li>
|
||||
<li><i class="pi pi-check-circle"></i> Modification par lot</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Feature 2 -->
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="pi pi-shield"></i>
|
||||
</div>
|
||||
<h3 class="feature-title">Attribution des rôles</h3>
|
||||
<p class="feature-description">
|
||||
Gérez les permissions de manière granulaire avec un système de rôles flexible et sécurisé conforme aux standards RBAC.
|
||||
</p>
|
||||
<ul class="feature-list">
|
||||
<li><i class="pi pi-check-circle"></i> Gestion RBAC complète</li>
|
||||
<li><i class="pi pi-check-circle"></i> Hiérarchie de rôles</li>
|
||||
<li><i class="pi pi-check-circle"></i> Permissions dynamiques</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Feature 3 -->
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="pi pi-chart-line"></i>
|
||||
</div>
|
||||
<h3 class="feature-title">Audit & Analytics</h3>
|
||||
<p class="feature-description">
|
||||
Suivez l'activité de vos utilisateurs avec des tableaux de bord interactifs et des rapports détaillés en temps réel.
|
||||
</p>
|
||||
<ul class="feature-list">
|
||||
<li><i class="pi pi-check-circle"></i> Logs d'authentification</li>
|
||||
<li><i class="pi pi-check-circle"></i> Rapports personnalisés</li>
|
||||
<li><i class="pi pi-check-circle"></i> Alertes de sécurité</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Feature 4 -->
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="pi pi-sync"></i>
|
||||
</div>
|
||||
<h3 class="feature-title">Synchronisation</h3>
|
||||
<p class="feature-description">
|
||||
Intégration transparente avec vos systèmes existants via API RESTful sécurisée et webhooks en temps réel.
|
||||
</p>
|
||||
<ul class="feature-list">
|
||||
<li><i class="pi pi-check-circle"></i> API REST complète</li>
|
||||
<li><i class="pi pi-check-circle"></i> Webhooks événementiels</li>
|
||||
<li><i class="pi pi-check-circle"></i> Connecteurs pré-configurés</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Feature 5 -->
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="pi pi-lock"></i>
|
||||
</div>
|
||||
<h3 class="feature-title">Sécurité avancée</h3>
|
||||
<p class="feature-description">
|
||||
Protection multi-niveaux avec chiffrement end-to-end, authentification multi-facteurs et audit de sécurité complet.
|
||||
</p>
|
||||
<ul class="feature-list">
|
||||
<li><i class="pi pi-check-circle"></i> MFA/2FA obligatoire</li>
|
||||
<li><i class="pi pi-check-circle"></i> Chiffrement AES-256</li>
|
||||
<li><i class="pi pi-check-circle"></i> SOC 2 Type II conforme</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Feature 6 -->
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">
|
||||
<i class="pi pi-cog"></i>
|
||||
</div>
|
||||
<h3 class="feature-title">Multi-tenant</h3>
|
||||
<p class="feature-description">
|
||||
Gérez plusieurs organisations et royaumes depuis une seule interface avec isolation complète des données.
|
||||
</p>
|
||||
<ul class="feature-list">
|
||||
<li><i class="pi pi-check-circle"></i> Isolation par royaume</li>
|
||||
<li><i class="pi pi-check-circle"></i> Personnalisation par org</li>
|
||||
<li><i class="pi pi-check-circle"></i> Délégation d'administration</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ==================== CTA SECTION ==================== -->
|
||||
<section class="cta-section">
|
||||
<div class="cta-container">
|
||||
<h2>Prêt à transformer votre gestion IAM ?</h2>
|
||||
<p>
|
||||
Rejoignez des centaines d'entreprises qui font confiance à Lions User Manager pour sécuriser et simplifier leur infrastructure d'identité.
|
||||
</p>
|
||||
<a href="/pages/user-manager/dashboard.xhtml" class="btn-cta-white">
|
||||
<i class="pi pi-sign-in"></i>
|
||||
Accéder à la plateforme maintenant
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ==================== FOOTER ==================== -->
|
||||
<footer class="footer">
|
||||
<div class="footer-container">
|
||||
<div class="footer-logo">
|
||||
<div class="footer-logo-icon">
|
||||
<i class="pi pi-users"></i>
|
||||
</div>
|
||||
<span class="footer-logo-text">Lions User Manager</span>
|
||||
</div>
|
||||
|
||||
<p class="footer-text">
|
||||
Plateforme professionnelle de gestion IAM propulsée par Keycloak Admin API
|
||||
</p>
|
||||
|
||||
<div class="footer-divider"></div>
|
||||
|
||||
<div class="footer-bottom">
|
||||
<span>© 2025 Lions User Manager</span>
|
||||
<span>•</span>
|
||||
<span><i class="pi pi-shield"></i> Sécurisé par OpenID Connect</span>
|
||||
<span>•</span>
|
||||
<span><i class="pi pi-server"></i> Powered by Quarkus & PrimeFaces Freya</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- ==================== SCRIPTS ==================== -->
|
||||
<script>
|
||||
// Session expired alert
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('expired') === 'true') {
|
||||
document.getElementById('sessionExpiredAlert').classList.add('show');
|
||||
}
|
||||
|
||||
// Navbar scroll effect
|
||||
window.addEventListener('scroll', () => {
|
||||
const navbar = document.getElementById('navbar');
|
||||
if (window.scrollY > 50) {
|
||||
navbar.classList.add('scrolled');
|
||||
} else {
|
||||
navbar.classList.remove('scrolled');
|
||||
}
|
||||
});
|
||||
|
||||
// Animated counters
|
||||
const animateCounter = (element) => {
|
||||
const target = parseInt(element.getAttribute('data-target'));
|
||||
const duration = 2000;
|
||||
const step = target / (duration / 16);
|
||||
let current = 0;
|
||||
|
||||
const timer = setInterval(() => {
|
||||
current += step;
|
||||
if (current >= target) {
|
||||
element.textContent = target.toLocaleString();
|
||||
clearInterval(timer);
|
||||
} else {
|
||||
element.textContent = Math.floor(current).toLocaleString();
|
||||
}
|
||||
}, 16);
|
||||
};
|
||||
|
||||
// Intersection Observer for counter animation
|
||||
const observerOptions = {
|
||||
threshold: 0.5,
|
||||
rootMargin: '0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting && entry.target.getAttribute('data-target')) {
|
||||
animateCounter(entry.target);
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
document.querySelectorAll('.stat-number[data-target]').forEach(el => {
|
||||
observer.observe(el);
|
||||
});
|
||||
|
||||
// Smooth scroll
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,58 +0,0 @@
|
||||
<!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>
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
<!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"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:define name="title">Affectation des Realms - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<h:form id="formRealmAssignments">
|
||||
<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-sitemap text-purple-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Affectation des Realms</h3>
|
||||
<p class="text-600 m-0">Gérer les permissions d'administration par realm (contrôle multi-tenant)</p>
|
||||
</div>
|
||||
</div>
|
||||
<p:commandButton value="Nouvelle Affectation"
|
||||
icon="pi pi-plus"
|
||||
styleClass="p-button-success"
|
||||
onclick="PF('assignRealmDialog').show();"
|
||||
type="button" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
STATISTIQUES
|
||||
================================================================ -->
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="card">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Total Affectations</div>
|
||||
<div class="text-900 font-bold text-2xl">#{realmAssignmentBean.totalAssignments}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-sitemap text-blue-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-500">Assignations configurées</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="card">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Affectations Actives</div>
|
||||
<div class="text-900 font-bold text-2xl">#{realmAssignmentBean.activeAssignmentsCount}</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>
|
||||
<small class="text-500">En cours de validité</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="card">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Super Admins</div>
|
||||
<div class="text-900 font-bold text-2xl">#{realmAssignmentBean.superAdminsCount}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-orange-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-star text-orange-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<small class="text-500">Peuvent gérer tous les realms</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
TABLEAU DES AFFECTATIONS
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center justify-content-between mb-4">
|
||||
<h5 class="m-0">Affectations Actuelles</h5>
|
||||
<p:commandButton value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
styleClass="p-button-outlined p-button-sm"
|
||||
action="#{realmAssignmentBean.loadAssignments}"
|
||||
update=":formRealmAssignments" />
|
||||
</div>
|
||||
|
||||
<p:messages id="messages" showDetail="true" closable="true">
|
||||
<p:autoUpdate />
|
||||
</p:messages>
|
||||
|
||||
<p:dataTable id="assignmentsTable"
|
||||
value="#{realmAssignmentBean.assignments}"
|
||||
var="assignment"
|
||||
paginator="true"
|
||||
rows="10"
|
||||
paginatorPosition="bottom"
|
||||
paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
|
||||
rowsPerPageTemplate="10,20,50"
|
||||
emptyMessage="Aucune affectation configurée"
|
||||
styleClass="p-datatable-sm">
|
||||
|
||||
<!-- Colonne Utilisateur -->
|
||||
<p:column headerText="Utilisateur" sortBy="#{assignment.username}" filterBy="#{assignment.username}" filterMatchMode="contains">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<div style="width: 32px; height: 32px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-color), var(--primary-600)); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: bold; color: white;">
|
||||
<h:outputText value="#{assignment.username != null and assignment.username.length() >= 2 ? assignment.username.substring(0,2).toUpperCase() : 'U'}" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-900 font-semibold">#{assignment.username}</div>
|
||||
<small class="text-500">#{assignment.email}</small>
|
||||
</div>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Realm -->
|
||||
<p:column headerText="Realm" sortBy="#{assignment.realmName}" filterBy="#{assignment.realmName}" filterMatchMode="contains">
|
||||
<p:tag value="#{assignment.realmName}"
|
||||
severity="info"
|
||||
icon="pi pi-globe" />
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Type -->
|
||||
<p:column headerText="Type" style="width: 150px">
|
||||
<p:tag value="Super Admin"
|
||||
severity="danger"
|
||||
icon="pi pi-star"
|
||||
rendered="#{assignment.isSuperAdmin()}" />
|
||||
<p:tag value="Realm Admin"
|
||||
severity="success"
|
||||
icon="pi pi-shield"
|
||||
rendered="#{!assignment.isSuperAdmin()}" />
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Statut -->
|
||||
<p:column headerText="Statut" style="width: 120px">
|
||||
<p:tag value="Actif"
|
||||
severity="success"
|
||||
icon="pi pi-check-circle"
|
||||
rendered="#{assignment.active and !assignment.isExpired()}" />
|
||||
<p:tag value="Inactif"
|
||||
severity="warning"
|
||||
icon="pi pi-times-circle"
|
||||
rendered="#{!assignment.active}" />
|
||||
<p:tag value="Expiré"
|
||||
severity="danger"
|
||||
icon="pi pi-exclamation-circle"
|
||||
rendered="#{assignment.isExpired()}" />
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Assigné le -->
|
||||
<p:column headerText="Assigné le" sortBy="#{assignment.assignedAt}" style="width: 180px">
|
||||
<h:outputText value="#{assignment.assignedAt}">
|
||||
<f:convertDateTime pattern="dd/MM/yyyy HH:mm" />
|
||||
</h:outputText>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Par -->
|
||||
<p:column headerText="Par" sortBy="#{assignment.assignedBy}" style="width: 150px">
|
||||
<h:outputText value="#{assignment.assignedBy}" />
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Actions -->
|
||||
<p:column headerText="Actions" style="width: 120px; text-align: center">
|
||||
<div class="flex gap-1 justify-content-center flex-wrap">
|
||||
<!-- Bouton Désactiver -->
|
||||
<p:commandButton icon="pi pi-ban"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-warning"
|
||||
title="Désactiver"
|
||||
action="#{realmAssignmentBean.deactivateAssignment(assignment)}"
|
||||
update=":formRealmAssignments"
|
||||
process="@this"
|
||||
rendered="#{assignment.active}">
|
||||
<p:confirm header="Confirmation"
|
||||
message="Désactiver cette affectation ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
|
||||
<!-- Bouton Activer -->
|
||||
<p:commandButton icon="pi pi-check"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-success"
|
||||
title="Activer"
|
||||
action="#{realmAssignmentBean.activateAssignment(assignment)}"
|
||||
update=":formRealmAssignments"
|
||||
process="@this"
|
||||
rendered="#{!assignment.active}">
|
||||
<p:confirm header="Confirmation"
|
||||
message="Activer cette affectation ?"
|
||||
icon="pi pi-question-circle" />
|
||||
</p:commandButton>
|
||||
|
||||
<!-- Bouton Supprimer -->
|
||||
<p:commandButton icon="pi pi-trash"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
|
||||
title="Supprimer"
|
||||
action="#{realmAssignmentBean.revokeAssignment(assignment)}"
|
||||
update=":formRealmAssignments"
|
||||
process="@this">
|
||||
<p:confirm header="Confirmation"
|
||||
message="Révoquer l'accès de #{assignment.username} au realm #{assignment.realmName} ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG D'AFFECTATION
|
||||
================================================================ -->
|
||||
<p:dialog header="Assigner un Realm à un Utilisateur"
|
||||
widgetVar="assignRealmDialog"
|
||||
modal="true"
|
||||
responsive="true"
|
||||
width="600"
|
||||
showEffect="fade"
|
||||
hideEffect="fade">
|
||||
<h:form id="formAssignRealm">
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<label class="block text-900 font-semibold mb-2">
|
||||
<i class="pi pi-user text-primary mr-1"></i>
|
||||
Utilisateur *
|
||||
</label>
|
||||
<p:selectOneMenu value="#{realmAssignmentBean.selectedUserId}"
|
||||
styleClass="w-full"
|
||||
filter="true"
|
||||
filterMatchMode="contains">
|
||||
<f:selectItem itemLabel="Sélectionner un utilisateur" itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{realmAssignmentBean.availableUsers}"
|
||||
var="user"
|
||||
itemValue="#{user.id}"
|
||||
itemLabel="#{user.username} (#{user.email})" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label class="block text-900 font-semibold mb-2">
|
||||
<i class="pi pi-globe text-primary mr-1"></i>
|
||||
Realm *
|
||||
</label>
|
||||
<p:selectOneMenu value="#{realmAssignmentBean.selectedRealmName}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner un realm" itemValue="" noSelectionOption="true" />
|
||||
<f:selectItems value="#{realmAssignmentBean.availableRealms}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label class="block text-900 font-semibold mb-2">
|
||||
<i class="pi pi-comment text-primary mr-1"></i>
|
||||
Raison
|
||||
</label>
|
||||
<p:inputText value="#{realmAssignmentBean.newAssignment.raison}"
|
||||
styleClass="w-full"
|
||||
placeholder="Ex: Nouveau gestionnaire du realm client" />
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<label class="block text-900 font-semibold mb-2">
|
||||
<i class="pi pi-file-edit text-primary mr-1"></i>
|
||||
Commentaires
|
||||
</label>
|
||||
<p:inputTextarea value="#{realmAssignmentBean.newAssignment.commentaires}"
|
||||
rows="3"
|
||||
styleClass="w-full"
|
||||
placeholder="Commentaires administratifs (optionnel)" />
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="flex align-items-center">
|
||||
<p:selectBooleanCheckbox value="#{realmAssignmentBean.newAssignment.temporaire}"
|
||||
itemLabel="Affectation temporaire"
|
||||
styleClass="mr-2" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12" rendered="#{realmAssignmentBean.newAssignment.temporaire}">
|
||||
<label class="block text-900 font-semibold mb-2">
|
||||
<i class="pi pi-calendar text-primary mr-1"></i>
|
||||
Date d'expiration
|
||||
</label>
|
||||
<p:calendar value="#{realmAssignmentBean.newAssignment.dateExpiration}"
|
||||
pattern="dd/MM/yyyy HH:mm"
|
||||
showTime="true"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="surface-100 border-round p-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-info-circle text-blue-500"></i>
|
||||
<div>
|
||||
<div class="text-700 font-semibold text-sm">Information</div>
|
||||
<small class="text-600">
|
||||
L'utilisateur pourra administrer uniquement le realm assigné.
|
||||
Pour accorder l'accès à tous les realms, utilisez le statut Super Admin.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<div class="flex gap-2">
|
||||
<p:commandButton value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-text flex-1"
|
||||
onclick="PF('assignRealmDialog').hide();"
|
||||
type="button"
|
||||
action="#{realmAssignmentBean.resetForm}" />
|
||||
<p:commandButton value="Assigner"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success flex-1"
|
||||
action="#{realmAssignmentBean.assignRealm}"
|
||||
update=":formRealmAssignments :formAssignRealm"
|
||||
oncomplete="if (args.validationFailed == false) PF('assignRealmDialog').hide();" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</p:dialog>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION
|
||||
================================================================ -->
|
||||
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade" responsive="true" width="400">
|
||||
<p:commandButton value="Non" type="button" styleClass="p-button-text" icon="pi pi-times" />
|
||||
<p:commandButton value="Oui" type="button" styleClass="p-button-danger" icon="pi pi-check" />
|
||||
</p:confirmDialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
@@ -4,176 +4,420 @@
|
||||
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:define name="title">Journal d'Audit - 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-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="action" value="#{auditConsultationBean.exportToCSV}" />
|
||||
<ui:param name="severity" value="success" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
|
||||
<!-- Statistiques -->
|
||||
<div class="grid mb-4">
|
||||
<div class="col-12 md:col-3">
|
||||
<ui:include src="/templates/components/audit/audit-stats-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>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<ui:include src="/templates/components/audit/audit-stats-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>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<ui:include src="/templates/components/audit/audit-stats-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>
|
||||
</div>
|
||||
<div class="col-12 md:col-3">
|
||||
<ui:include src="/templates/components/audit/audit-stats-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 pi-percentage" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filtres de recherche -->
|
||||
<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="acteurFilter" value="Acteur" />
|
||||
<p:inputText id="acteurFilter"
|
||||
value="#{auditConsultationBean.acteurUsername}"
|
||||
placeholder="Nom d'utilisateur..."
|
||||
styleClass="w-full" />
|
||||
|
||||
<p:outputLabel for="typeActionFilter" value="Type d'action" />
|
||||
<p:selectOneMenu id="typeActionFilter"
|
||||
value="#{auditConsultationBean.selectedTypeAction}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les types" itemValue="" />
|
||||
<f:selectItems value="#{auditConsultationBean.typeActionOptions}" />
|
||||
</p:selectOneMenu>
|
||||
|
||||
<p:outputLabel for="succesFilter" value="Résultat" />
|
||||
<p:selectOneMenu id="succesFilter"
|
||||
value="#{auditConsultationBean.succes}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous" itemValue="" />
|
||||
<f:selectItem itemLabel="Succès" itemValue="true" />
|
||||
<f:selectItem itemLabel="Échec" itemValue="false" />
|
||||
</p:selectOneMenu>
|
||||
|
||||
<p:outputLabel for="dateDebutFilter" value="Date début" />
|
||||
<p:calendar id="dateDebutFilter"
|
||||
value="#{auditConsultationBean.dateDebut}"
|
||||
pattern="dd/MM/yyyy"
|
||||
styleClass="w-full" />
|
||||
|
||||
<p:outputLabel for="dateFinFilter" value="Date fin" />
|
||||
<p:calendar id="dateFinFilter"
|
||||
value="#{auditConsultationBean.dateFin}"
|
||||
pattern="dd/MM/yyyy"
|
||||
styleClass="w-full" />
|
||||
|
||||
<p:outputLabel for="ressourceFilter" value="Type ressource" />
|
||||
<p:inputText id="ressourceFilter"
|
||||
value="#{auditConsultationBean.ressourceType}"
|
||||
placeholder="USER, ROLE..."
|
||||
styleClass="w-full" />
|
||||
</p:panelGrid>
|
||||
|
||||
<div class="flex gap-2 justify-content-end mt-3">
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Rechercher" />
|
||||
<ui:param name="icon" value="pi pi-search" />
|
||||
<ui:param name="action" value="#{auditConsultationBean.searchLogs}" />
|
||||
<ui:param name="update" value="auditLogsList" />
|
||||
<ui:param name="severity" value="primary" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Réinitialiser" />
|
||||
<ui:param name="icon" value="pi pi-refresh" />
|
||||
<ui:param name="action" value="#{auditConsultationBean.resetFilters}" />
|
||||
<ui:param name="update" value="auditLogsList formFilters" />
|
||||
<ui:param name="severity" value="secondary" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
|
||||
<!-- Liste des logs -->
|
||||
<div class="card">
|
||||
<h:form id="formAuditLogs">
|
||||
<h5>Logs d'Audit</h5>
|
||||
<div id="auditLogsList" class="flex flex-column gap-2">
|
||||
<c:forEach var="log" items="#{auditConsultationBean.auditLogs}">
|
||||
<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="false" />
|
||||
</ui:include>
|
||||
</c:forEach>
|
||||
<c:if test="#{empty auditConsultationBean.auditLogs}">
|
||||
<p class="text-center text-color-secondary">Aucun log d'audit trouvé</p>
|
||||
</c:if>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="flex justify-content-between align-items-center mt-3">
|
||||
<span class="text-600">
|
||||
Affichage de #{auditConsultationBean.currentPage * auditConsultationBean.pageSize + 1}
|
||||
à #{auditConsultationBean.currentPage * auditConsultationBean.pageSize + auditConsultationBean.auditLogs.size()}
|
||||
sur #{auditConsultationBean.totalRecords}
|
||||
</span>
|
||||
<div class="flex gap-2">
|
||||
<p:commandButton
|
||||
value="Précédent"
|
||||
icon="pi pi-arrow-left"
|
||||
disabled="#{auditConsultationBean.currentPage == 0}"
|
||||
action="#{auditConsultationBean.currentPage = auditConsultationBean.currentPage - 1; auditConsultationBean.searchLogs()}"
|
||||
update="auditLogsList" />
|
||||
<p:commandButton
|
||||
value="Suivant"
|
||||
icon="pi pi-arrow-right"
|
||||
iconPos="right"
|
||||
disabled="#{(auditConsultationBean.currentPage + 1) * auditConsultationBean.pageSize >= auditConsultationBean.totalRecords}"
|
||||
action="#{auditConsultationBean.currentPage = auditConsultationBean.currentPage + 1; auditConsultationBean.searchLogs()}"
|
||||
update="auditLogsList" />
|
||||
<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-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>
|
||||
</div>
|
||||
</div>
|
||||
<h:form id="formHeaderActions">
|
||||
<p:commandButton
|
||||
value="Exporter CSV"
|
||||
icon="pi pi-download"
|
||||
styleClass="p-button-success"
|
||||
action="#{auditConsultationBean.exportToCSV}"
|
||||
ajax="false" />
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
STATISTIQUES KPI (4 CARTES)
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3">Statistiques d'Audit</h5>
|
||||
</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
|
||||
================================================================ -->
|
||||
<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">
|
||||
<label for="acteurFilter" class="block text-900 font-medium mb-2">Acteur</label>
|
||||
<p:inputText id="acteurFilter"
|
||||
value="#{auditConsultationBean.acteurUsername}"
|
||||
placeholder="Nom d'utilisateur..."
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<label for="typeActionFilter" class="block text-900 font-medium mb-2">Type d'action</label>
|
||||
<p:selectOneMenu id="typeActionFilter"
|
||||
value="#{auditConsultationBean.selectedTypeAction}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les types" itemValue="" />
|
||||
<f:selectItems value="#{auditConsultationBean.typeActionOptions}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<label for="succesFilter" class="block text-900 font-medium mb-2">Résultat</label>
|
||||
<p:selectOneMenu id="succesFilter"
|
||||
value="#{auditConsultationBean.succes}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous" itemValue="" />
|
||||
<f:selectItem itemLabel="Succès" itemValue="true" />
|
||||
<f:selectItem itemLabel="Échec" itemValue="false" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<label for="dateDebutFilter" class="block text-900 font-medium mb-2">Date début</label>
|
||||
<p:calendar id="dateDebutFilter"
|
||||
value="#{auditConsultationBean.dateDebut}"
|
||||
pattern="dd/MM/yyyy"
|
||||
showIcon="true"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<label for="dateFinFilter" class="block text-900 font-medium mb-2">Date fin</label>
|
||||
<p:calendar id="dateFinFilter"
|
||||
value="#{auditConsultationBean.dateFin}"
|
||||
pattern="dd/MM/yyyy"
|
||||
showIcon="true"
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<label for="ressourceFilter" class="block text-900 font-medium mb-2">Type ressource</label>
|
||||
<p:inputText id="ressourceFilter"
|
||||
value="#{auditConsultationBean.ressourceType}"
|
||||
placeholder="USER, ROLE, CLIENT..."
|
||||
styleClass="w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 justify-content-end mt-4">
|
||||
<p:commandButton
|
||||
value="Rechercher"
|
||||
icon="pi pi-search"
|
||||
styleClass="p-button-primary"
|
||||
action="#{auditConsultationBean.searchLogs}"
|
||||
update=":formAuditLogs:auditLogsTable" />
|
||||
<p:commandButton
|
||||
value="Réinitialiser"
|
||||
icon="pi pi-refresh"
|
||||
styleClass="p-button-secondary"
|
||||
action="#{auditConsultationBean.resetFilters}"
|
||||
update=":formAuditLogs:auditLogsTable @form" />
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
TABLEAU DES LOGS D'AUDIT
|
||||
================================================================ -->
|
||||
<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>
|
||||
<p: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é"
|
||||
reflow="true">
|
||||
|
||||
<!-- Colonne Statut -->
|
||||
<p:column headerText="Statut" style="width: 100px; text-align: center">
|
||||
<p:tag value="#{log.succes ? 'Succès' : 'Échec'}"
|
||||
severity="#{log.succes ? 'success' : 'danger'}" />
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Type d'action -->
|
||||
<p:column headerText="Type d'action" sortBy="#{log.typeAction}" style="width: 180px">
|
||||
<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>
|
||||
|
||||
<!-- Colonne Acteur -->
|
||||
<p:column headerText="Acteur" sortBy="#{log.acteurUsername}" style="width: 200px">
|
||||
<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>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Ressource -->
|
||||
<p:column headerText="Ressource" style="width: 150px">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-database text-blue-500"></i>
|
||||
<span class="text-900">#{log.ressourceType}</span>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Date -->
|
||||
<p:column headerText="Date" sortBy="#{log.dateAction}" style="width: 180px">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-calendar text-purple-500"></i>
|
||||
<span class="text-900">#{log.dateAction}</span>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Détails -->
|
||||
<p:column headerText="Détails" style="width: 250px">
|
||||
<span class="text-600 text-sm" style="display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
#{not empty log.details ? log.details : '-'}
|
||||
</span>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne IP -->
|
||||
<p:column headerText="Adresse IP" style="width: 130px">
|
||||
<span class="text-600 text-sm font-mono">
|
||||
#{not empty log.adresseIp ? log.adresseIp : '-'}
|
||||
</span>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Actions -->
|
||||
<p:column headerText="Actions" style="width: 80px; text-align: center">
|
||||
<p:commandButton
|
||||
icon="pi pi-eye"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-info"
|
||||
title="Voir les détails"
|
||||
onclick="PF('auditLogDetailsDialog').show()">
|
||||
<f:setPropertyActionListener target="#{auditConsultationBean.selectedLog}" value="#{log}" />
|
||||
</p:commandButton>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</h:form>
|
||||
</div>
|
||||
</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">
|
||||
<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>
|
||||
<p: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}">
|
||||
<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>
|
||||
</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>
|
||||
</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>
|
||||
</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>
|
||||
</h:panelGroup>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-content-end mt-4">
|
||||
<p:commandButton
|
||||
value="Fermer"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
onclick="PF('auditLogDetailsDialog').hide()"
|
||||
type="button" />
|
||||
</div>
|
||||
</h:form>
|
||||
</p:dialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
|
||||
@@ -7,154 +7,327 @@
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:define name="title">Tableau de Bord - Lions User Manager</ui:define>
|
||||
|
||||
|
||||
<ui:define name="content">
|
||||
<div class="grid">
|
||||
<!-- En-tête -->
|
||||
<div class="col-12">
|
||||
<ui:include src="/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:include>
|
||||
</div>
|
||||
|
||||
<!-- KPIs Principaux -->
|
||||
<div class="col-12">
|
||||
<div class="grid">
|
||||
<!-- 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="#{empty dashboardBean ? '-' : dashboardBean.totalUsers}" />
|
||||
<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="#{empty dashboardBean ? '-' : dashboardBean.totalRoles}" />
|
||||
<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="#{empty dashboardBean ? '-' : dashboardBean.recentActions}" />
|
||||
<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: Sessions Actives -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Sessions Actives" />
|
||||
<ui:param name="value" value="#{empty dashboardBean ? '-' : dashboardBean.activeSessions}" />
|
||||
<ui:param name="icon" value="pi-sign-in" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="subtitle" value="Utilisateurs connectés" />
|
||||
<ui:param name="statusIcon" value="pi-check-circle" />
|
||||
<ui:param name="statusLabel" value="En ligne" />
|
||||
<ui:param name="statusValue" value="#{empty dashboardBean ? '0' : dashboardBean.onlineUsers} actifs" />
|
||||
</ui:include>
|
||||
<h:form id="formDashboard">
|
||||
<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-home text-blue-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Tableau de Bord</h3>
|
||||
<p class="text-600 m-0">Vue d'ensemble de la gestion des utilisateurs Keycloak</p>
|
||||
</div>
|
||||
</div>
|
||||
<p:commandButton
|
||||
value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
styleClass="p-button-secondary"
|
||||
action="#{dashboardBean.refreshStatistics}"
|
||||
update=":formDashboard" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Rapides -->
|
||||
<ui:include src="/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">
|
||||
<h:form>
|
||||
<p:commandButton
|
||||
|
||||
<!-- ================================================================
|
||||
STATISTIQUES PRINCIPALES (4 KPI CARDS)
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3">Statistiques Principales</h5>
|
||||
</div>
|
||||
|
||||
<!-- KPI 1: Utilisateurs Actifs -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 border-round-lg hover:surface-100 cursor-pointer transition-colors transition-duration-150">
|
||||
<p:commandButton
|
||||
styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
|
||||
outcome="/pages/user-manager/users/list">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Utilisateurs Actifs</div>
|
||||
<div class="text-900 font-bold text-2xl">#{dashboardBean.totalUsersDisplay}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-users text-blue-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-arrow-right text-600"></i>
|
||||
<span class="ml-2">Total utilisateurs</span>
|
||||
</div>
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI 2: Rôles Realm -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 border-round-lg hover:surface-100 cursor-pointer transition-colors transition-duration-150">
|
||||
<p:commandButton
|
||||
styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
|
||||
outcome="/pages/user-manager/roles/list">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Rôles Realm</div>
|
||||
<div class="text-900 font-bold text-2xl">#{dashboardBean.totalRolesDisplay}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-green-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-shield text-green-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-arrow-right text-600"></i>
|
||||
<span class="ml-2">Rôles configurés</span>
|
||||
</div>
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI 3: Actions Récentes -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 border-round-lg hover:surface-100 cursor-pointer transition-colors transition-duration-150">
|
||||
<p:commandButton
|
||||
styleClass="p-0 w-full text-left border-none bg-transparent hover:bg-transparent active:bg-transparent"
|
||||
outcome="/pages/user-manager/audit/logs">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Actions Récentes</div>
|
||||
<div class="text-900 font-bold text-2xl">#{dashboardBean.recentActionsDisplay}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-orange-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-history text-orange-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-arrow-right text-600"></i>
|
||||
<span class="ml-2">Dernières 24h</span>
|
||||
</div>
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI 4: Taux d'Activation -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 border-round-lg">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Realm Actif</div>
|
||||
<div class="text-900 font-bold text-xl" style="word-break: break-word;">lions-user-manager</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-globe text-purple-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<p:tag value="Opérationnel" severity="success" styleClass="text-xs" />
|
||||
<span class="text-500 text-sm">Realm Keycloak</span>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<p:commandButton
|
||||
value="Nouvel Utilisateur"
|
||||
icon="pi pi-user-plus"
|
||||
styleClass="w-full p-button-success"
|
||||
styleClass="w-full p-button-success mb-2"
|
||||
outcome="/pages/user-manager/users/create" />
|
||||
</h:form>
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<h:form>
|
||||
<p:commandButton
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<p:commandButton
|
||||
value="Liste des Utilisateurs"
|
||||
icon="pi pi-users"
|
||||
styleClass="w-full p-button-primary"
|
||||
styleClass="w-full p-button-primary mb-2"
|
||||
outcome="/pages/user-manager/users/list" />
|
||||
</h:form>
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<h:form>
|
||||
<p:commandButton
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<p:commandButton
|
||||
value="Gestion des Rôles"
|
||||
icon="pi pi-shield"
|
||||
styleClass="w-full p-button-info"
|
||||
styleClass="w-full p-button-info mb-2"
|
||||
outcome="/pages/user-manager/roles/list" />
|
||||
</h:form>
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<h:form>
|
||||
<p:commandButton
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<p:commandButton
|
||||
value="Journal d'Audit"
|
||||
icon="pi pi-history"
|
||||
styleClass="w-full p-button-help"
|
||||
styleClass="w-full p-button-help mb-2"
|
||||
outcome="/pages/user-manager/audit/logs" />
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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 les raccourcis ci-dessus pour accéder rapidement aux fonctionnalités principales</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
|
||||
<!-- Informations Système -->
|
||||
<ui:include src="/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>
|
||||
|
||||
<!-- ================================================================
|
||||
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-blue-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Informations Système</h5>
|
||||
</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 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</span>
|
||||
</div>
|
||||
<span class="font-semibold text-900">1.0.0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Realm Keycloak -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-globe text-500"></i>
|
||||
<span class="text-600 font-medium">Realm Keycloak</span>
|
||||
</div>
|
||||
<span class="font-semibold text-900">lions-user-manager</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statut -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-check-circle text-500"></i>
|
||||
<span class="text-600 font-medium">Statut</span>
|
||||
</div>
|
||||
<p:tag value="Opérationnel" severity="success" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Framework -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<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 3.15.1</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interface -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-palette text-500"></i>
|
||||
<span class="text-600 font-medium">Interface</span>
|
||||
</div>
|
||||
<span class="font-semibold text-900">PrimeFaces Freya</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Environnement -->
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-server text-500"></i>
|
||||
<span class="text-600 font-medium">Environnement</span>
|
||||
</div>
|
||||
<p:tag value="Développement" severity="warning" styleClass="text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
ACTIVITÉS RÉCENTES
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center justify-content-between mb-4">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-clock text-purple-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Activités Récentes</h5>
|
||||
</div>
|
||||
<p:commandButton
|
||||
value="Voir tout"
|
||||
icon="pi pi-arrow-right"
|
||||
styleClass="p-button-text p-button-sm"
|
||||
outcome="/pages/user-manager/audit/logs" />
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Statistique 1: Utilisateurs créés aujourd'hui -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="surface-50 border-round p-3 text-center">
|
||||
<i class="pi pi-user-plus text-blue-500 mb-2" style="font-size: 2rem"></i>
|
||||
<div class="text-900 font-bold text-xl mb-1">0</div>
|
||||
<div class="text-600 text-sm">Utilisateurs créés</div>
|
||||
<small class="text-500">Aujourd'hui</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistique 2: Rôles modifiés -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="surface-50 border-round p-3 text-center">
|
||||
<i class="pi pi-shield text-green-500 mb-2" style="font-size: 2rem"></i>
|
||||
<div class="text-900 font-bold text-xl mb-1">0</div>
|
||||
<div class="text-600 text-sm">Rôles modifiés</div>
|
||||
<small class="text-500">Cette semaine</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistique 3: Sessions actives -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="surface-50 border-round p-3 text-center">
|
||||
<i class="pi pi-circle-fill text-orange-500 mb-2" style="font-size: 2rem; animation: pulse 2s ease-in-out infinite;"></i>
|
||||
<div class="text-900 font-bold text-xl mb-1">-</div>
|
||||
<div class="text-600 text-sm">Sessions actives</div>
|
||||
<small class="text-500">En temps réel</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistique 4: Actions critiques -->
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="surface-50 border-round p-3 text-center">
|
||||
<i class="pi pi-exclamation-triangle text-red-500 mb-2" style="font-size: 2rem"></i>
|
||||
<div class="text-900 font-bold text-xl mb-1">0</div>
|
||||
<div class="text-600 text-sm">Actions critiques</div>
|
||||
<small class="text-500">24 dernières heures</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
|
||||
|
||||
</ui:composition>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html" xmlns:f="jakarta.faces.core"
|
||||
xmlns:ui="jakarta.faces.facelets" xmlns:p="http://primefaces.org/ui" template="/template.xhtml">
|
||||
|
||||
<ui:define name="title">Gestion Rôles</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<h:form id="form">
|
||||
<div class="card">
|
||||
<p:toolbar>
|
||||
<p:toolbarGroup>
|
||||
<p:commandButton value="Nouveau" icon="pi pi-plus" actionListener="#{roleView.openNew}"
|
||||
update=":dialogs:manage-role-content" oncomplete="PF('manageRoleDialog').show()"
|
||||
styleClass="ui-button-success" style="margin-right: .5rem" />
|
||||
</p:toolbarGroup>
|
||||
</p:toolbar>
|
||||
|
||||
<p:dataTable id="dt-roles" widgetVar="dtRoles" var="role" value="#{roleView.roles}" reflow="true"
|
||||
styleClass="products-table" selection="#{roleView.selectedRole}" rowKey="#{role.id}"
|
||||
paginator="true" rows="10" paginatorPosition="bottom">
|
||||
|
||||
<f:facet name="header">
|
||||
<div class="products-table-header">
|
||||
<span style="font-weight: bold">Rôles</span>
|
||||
</div>
|
||||
</f:facet>
|
||||
|
||||
<p:column headerText="Nom" sortBy="#{role.name}">
|
||||
<h:outputText value="#{role.name}" />
|
||||
</p:column>
|
||||
<p:column headerText="Description" sortBy="#{role.description}">
|
||||
<h:outputText value="#{role.description}" />
|
||||
</p:column>
|
||||
<p:column headerText="Composite">
|
||||
<p:tag value="Composite" severity="warning" rendered="#{role.composite}" />
|
||||
</p:column>
|
||||
|
||||
<p:column exportable="false">
|
||||
<p:commandButton icon="pi pi-pencil" update=":dialogs:manage-role-content"
|
||||
oncomplete="PF('manageRoleDialog').show()"
|
||||
styleClass="edit-button rounded-button ui-button-success" process="@this"
|
||||
style="margin-right: 5px;">
|
||||
<f:setPropertyActionListener value="#{role}" target="#{roleView.selectedRole}" />
|
||||
<p:resetInput target=":dialogs:manage-role-content" />
|
||||
</p:commandButton>
|
||||
<p:commandButton class="ui-button-warning rounded-button" icon="pi pi-trash" process="@this"
|
||||
oncomplete="PF('deleteRoleDialog').show()">
|
||||
<f:setPropertyActionListener value="#{role}" target="#{roleView.selectedRole}" />
|
||||
</p:commandButton>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</div>
|
||||
</h:form>
|
||||
|
||||
<h:form id="dialogs">
|
||||
<p:dialog header="Détails Rôle" showEffect="fade" modal="true" widgetVar="manageRoleDialog"
|
||||
responsive="true" width="450">
|
||||
<p:outputPanel id="manage-role-content" class="ui-fluid">
|
||||
<p:outputPanel rendered="#{not empty roleView.selectedRole}">
|
||||
<div class="field" style="margin-bottom: 1rem;">
|
||||
<p:outputLabel for="name">Nom</p:outputLabel>
|
||||
<p:inputText id="name" value="#{roleView.selectedRole.name}" required="true"
|
||||
disabled="#{not empty roleView.selectedRole.id}" />
|
||||
</div>
|
||||
<div class="field" style="margin-bottom: 1rem;">
|
||||
<p:outputLabel for="description">Description</p:outputLabel>
|
||||
<p:inputTextarea id="description" value="#{roleView.selectedRole.description}" />
|
||||
</div>
|
||||
</p:outputPanel>
|
||||
</p:outputPanel>
|
||||
|
||||
<f:facet name="footer">
|
||||
<p:commandButton value="Sauvegarder" icon="pi pi-check" actionListener="#{roleView.saveRole}"
|
||||
update="manage-role-content :form:dt-roles" process="manage-role-content @this"
|
||||
oncomplete="if (!args.validationFailed) PF('manageRoleDialog').hide()" />
|
||||
<p:commandButton value="Annuler" icon="pi pi-times" onclick="PF('manageRoleDialog').hide()"
|
||||
class="ui-button-secondary" type="button" />
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
|
||||
<p:confirmDialog widgetVar="deleteRoleDialog" showEffect="fade" width="300" message="Supprimer ce rôle ?"
|
||||
header="Confirmation" severity="warn">
|
||||
<p:commandButton value="Oui" icon="pi pi-check" actionListener="#{roleView.deleteRole}" process="@this"
|
||||
oncomplete="PF('deleteRoleDialog').hide()" update=":form:dt-roles" />
|
||||
<p:commandButton value="Non" type="button" styleClass="ui-button-secondary" icon="pi pi-times"
|
||||
onclick="PF('deleteRoleDialog').hide()" />
|
||||
</p:confirmDialog>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
@@ -4,29 +4,301 @@
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:fr="http://primefaces.org/freya"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<f:metadata>
|
||||
<f:viewParam name="userId" value="#{userProfilBean.userId}" />
|
||||
<f:viewParam name="realm" value="#{userProfilBean.realmName}" />
|
||||
</f:metadata>
|
||||
|
||||
<ui:param name="page" value="#{userProfilBean}"/>
|
||||
<ui:define name="title">Attribution de Rôles - Lions User Manager</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<!-- En-tête -->
|
||||
<ui:include src="/templates/components/layout/page-header.xhtml">
|
||||
<ui:param name="icon" value="pi pi-key text-purple-500" />
|
||||
<ui:param name="title" value="Attribution de Rôles" />
|
||||
<ui:param name="description" value="Gérer les rôles de l'utilisateur" />
|
||||
</ui:include>
|
||||
<div class="grid">
|
||||
<!-- ================================================================
|
||||
EN-TÊTE DE LA PAGE
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-key text-purple-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Attribution de Rôles</h3>
|
||||
<p class="text-600 m-0">Gérer les rôles de l'utilisateur</p>
|
||||
</div>
|
||||
</div>
|
||||
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button p-button-text">
|
||||
<i class="pi pi-arrow-left mr-2"></i>
|
||||
Retour à la liste
|
||||
</h:link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attribution de rôles -->
|
||||
<div class="card">
|
||||
<ui:include src="/templates/components/role-management/role-assignment.xhtml">
|
||||
<ui:param name="user" value="#{userProfilBean.user}" />
|
||||
<ui:param name="availableRoles" value="#{roleGestionBean.allRoles}" />
|
||||
<ui:param name="userRoles" value="#{roleGestionBean.getUserRolesDTOs(userProfilBean.user)}" />
|
||||
<ui:param name="update" value="roleAssignmentPanel" />
|
||||
</ui:include>
|
||||
<!-- ================================================================
|
||||
INFORMATIONS UTILISATEUR
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-user text-blue-500"></i>
|
||||
Informations de l'Utilisateur
|
||||
</h3>
|
||||
|
||||
<h:panelGroup rendered="#{userProfilBean.user != null}">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="surface-50 border-round p-3 text-center">
|
||||
<!-- Avatar -->
|
||||
<div style="width: 80px; height: 80px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-color), var(--primary-600, #387FE9)); display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem auto; font-size: 2rem; font-weight: bold; color: white; box-shadow: 0 4px 12px rgba(0,0,0,0.12);">
|
||||
<h:outputText value="#{userProfilBean.user.username.substring(0,2).toUpperCase()}" />
|
||||
</div>
|
||||
<h4 class="text-900 font-semibold m-0 mb-1">#{userProfilBean.user.username}</h4>
|
||||
<p class="text-600 m-0 text-sm">#{userProfilBean.user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-8">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Prénom</label>
|
||||
<p class="text-900 m-0">#{userProfilBean.user.prenom}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Nom</label>
|
||||
<p class="text-900 m-0">#{userProfilBean.user.nom}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Email</label>
|
||||
<p class="text-900 m-0">#{userProfilBean.user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="mb-0">
|
||||
<label class="block text-600 font-medium mb-2 text-sm">Statut</label>
|
||||
<div class="flex align-items-center">
|
||||
<span class="inline-flex align-items-center px-2 py-1 border-round text-xs font-semibold"
|
||||
style="background-color: #{userProfilBean.user.enabled ? '#C8E6C9' : '#FFCDD2'}; color: #{userProfilBean.user.enabled ? '#2E7D32' : '#C62828'};">
|
||||
<i class="pi #{userProfilBean.user.enabled ? 'pi-check-circle' : 'pi-times-circle'} mr-1"></i>
|
||||
<h:outputText value="Actif" rendered="#{userProfilBean.user.enabled}" />
|
||||
<h:outputText value="Inactif" rendered="#{!userProfilBean.user.enabled}" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
|
||||
<h:panelGroup rendered="#{userProfilBean.user == null}">
|
||||
<div class="text-center p-5">
|
||||
<i class="pi pi-exclamation-triangle text-orange-500" style="font-size: 4rem"></i>
|
||||
<h4 class="text-900 mt-4 mb-2">Utilisateur non trouvé</h4>
|
||||
<p class="text-600 mb-3">
|
||||
<h:outputText value="Aucun ID d'utilisateur fourni" rendered="#{userProfilBean.userId == null or userProfilBean.userId == ''}" />
|
||||
<h:outputText value="L'utilisateur avec l'ID '#{userProfilBean.userId}' n'existe pas dans le realm '#{userProfilBean.realmName}'" rendered="#{userProfilBean.userId != null and userProfilBean.userId != ''}" />
|
||||
</p>
|
||||
<small class="text-500 block mb-4">Pour assigner des rôles, accédez à cette page depuis la liste des utilisateurs</small>
|
||||
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button p-button-primary">
|
||||
<i class="pi pi-list mr-2"></i>
|
||||
Aller à la liste des utilisateurs
|
||||
</h:link>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
GESTION DES RÔLES
|
||||
================================================================ -->
|
||||
<h:panelGroup rendered="#{userProfilBean.user != null}">
|
||||
<div class="col-12 lg:col-6">
|
||||
<div class="card h-full">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-shield text-green-500"></i>
|
||||
Rôles Actuels
|
||||
</h3>
|
||||
|
||||
<h:form id="formCurrentRoles">
|
||||
<!-- Liste des rôles actuels -->
|
||||
<div class="flex flex-column gap-2">
|
||||
<ui:repeat value="#{userProfilBean.user.realmRoles}" var="role">
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex align-items-center gap-2 flex-grow-1">
|
||||
<i class="pi pi-tag text-purple-500"></i>
|
||||
<div>
|
||||
<div class="text-900 font-semibold">#{role}</div>
|
||||
<small class="text-500">Rôle Realm</small>
|
||||
</div>
|
||||
</div>
|
||||
<p:commandButton icon="pi pi-times"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
|
||||
title="Retirer ce rôle"
|
||||
action="#{roleGestionBean.revokeRoleFromUser(userProfilBean.userId, role)}"
|
||||
update=":formCurrentRoles :formAvailableRoles"
|
||||
oncomplete="PF('formCurrentRoles').refresh();">
|
||||
<p:confirm header="Confirmation"
|
||||
message="Voulez-vous vraiment retirer le rôle '#{role}' ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
</ui:repeat>
|
||||
|
||||
<!-- Message si aucun rôle -->
|
||||
<div class="text-center p-4" rendered="#{userProfilBean.user.realmRoles == null or userProfilBean.user.realmRoles.size() == 0}">
|
||||
<i class="pi pi-inbox text-400" style="font-size: 2.5rem"></i>
|
||||
<p class="text-600 mt-3 mb-0">Aucun rôle assigné</p>
|
||||
<small class="text-500">Assignez des rôles depuis la liste disponible</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex align-items-center justify-content-between surface-100 border-round p-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-info-circle text-blue-500"></i>
|
||||
<span class="text-700 font-semibold">Total: #{userProfilBean.user.realmRoles != null ? userProfilBean.user.realmRoles.size() : 0} rôle(s)</span>
|
||||
</div>
|
||||
<fr:commandButton value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
outlined="true"
|
||||
size="small"
|
||||
action="#{userProfilBean.loadUser}"
|
||||
update=":formCurrentRoles :formAvailableRoles" />
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 lg:col-6">
|
||||
<div class="card h-full">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-plus-circle text-blue-500"></i>
|
||||
Rôles Disponibles
|
||||
</h3>
|
||||
|
||||
<h:form id="formAvailableRoles">
|
||||
<p:messages id="messages" showDetail="true" closable="true">
|
||||
<p:autoUpdate />
|
||||
</p:messages>
|
||||
|
||||
<!-- Liste des rôles disponibles -->
|
||||
<div class="flex flex-column gap-2">
|
||||
<ui:repeat value="#{roleGestionBean.realmRoles}" var="role">
|
||||
<!-- N'afficher que si le rôle n'est pas déjà assigné -->
|
||||
<h:panelGroup rendered="#{!userProfilBean.user.realmRoles.contains(role.name)}">
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<div class="flex-grow-1">
|
||||
<div class="text-900 font-semibold flex align-items-center gap-2 mb-1">
|
||||
<i class="pi pi-tag text-blue-500"></i>
|
||||
<span>#{role.name}</span>
|
||||
</div>
|
||||
<p class="text-600 text-sm m-0">
|
||||
<h:outputText value="#{role.description}" rendered="#{role.description != null and role.description != ''}" />
|
||||
<h:outputText value="Aucune description" styleClass="text-500 italic" rendered="#{role.description == null or role.description == ''}" />
|
||||
</p>
|
||||
</div>
|
||||
<p:commandButton icon="pi pi-plus"
|
||||
styleClass="p-button-rounded p-button-success p-button-sm"
|
||||
title="Assigner ce rôle"
|
||||
action="#{roleGestionBean.assignRoleToUser(userProfilBean.userId, role.name)}"
|
||||
update=":formCurrentRoles :formAvailableRoles"
|
||||
oncomplete="PF('formAvailableRoles').refresh();" />
|
||||
</div>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
</ui:repeat>
|
||||
|
||||
<!-- Message si aucun rôle disponible -->
|
||||
<div class="text-center p-4" rendered="#{roleGestionBean.realmRoles == null or roleGestionBean.realmRoles.size() == 0}">
|
||||
<i class="pi pi-inbox text-400" style="font-size: 2.5rem"></i>
|
||||
<p class="text-600 mt-3 mb-0">Aucun rôle disponible</p>
|
||||
<small class="text-500">Créez des rôles depuis la page de gestion des rôles</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 surface-100 border-round p-3">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-lightbulb text-orange-500"></i>
|
||||
<div>
|
||||
<div class="text-700 font-semibold text-sm">Astuce</div>
|
||||
<small class="text-600">Cliquez sur <i class="pi pi-plus"></i> pour assigner un rôle à l'utilisateur</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
ACTIONS
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-gray-500"></i>
|
||||
Actions
|
||||
</h3>
|
||||
|
||||
<h:form id="formActions">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<h:link outcome="/pages/user-manager/users/profile"
|
||||
styleClass="p-button p-button-outlined">
|
||||
<f:param name="userId" value="#{userProfilBean.userId}" />
|
||||
<i class="pi pi-user mr-2"></i>
|
||||
<span>Voir le Profil</span>
|
||||
</h:link>
|
||||
|
||||
<h:link outcome="/pages/user-manager/users/edit"
|
||||
styleClass="p-button p-button-outlined">
|
||||
<f:param name="userId" value="#{userProfilBean.userId}" />
|
||||
<i class="pi pi-pencil mr-2"></i>
|
||||
<span>Modifier l'Utilisateur</span>
|
||||
</h:link>
|
||||
|
||||
<h:link outcome="/pages/user-manager/users/list"
|
||||
styleClass="p-button p-button-outlined p-button-secondary">
|
||||
<i class="pi pi-list mr-2"></i>
|
||||
<span>Liste des Utilisateurs</span>
|
||||
</h:link>
|
||||
|
||||
<h:link outcome="/pages/user-manager/roles/list"
|
||||
styleClass="p-button p-button-outlined p-button-info">
|
||||
<i class="pi pi-shield mr-2"></i>
|
||||
<span>Gérer les Rôles</span>
|
||||
</h:link>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</h:panelGroup>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION
|
||||
================================================================ -->
|
||||
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
|
||||
responsive="true" width="400">
|
||||
<p:commandButton value="Non" type="button"
|
||||
styleClass="p-button-text"
|
||||
icon="pi pi-times" />
|
||||
<p:commandButton value="Oui" type="button"
|
||||
styleClass="p-button-danger"
|
||||
icon="pi pi-check" />
|
||||
</p:confirmDialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
|
||||
@@ -4,159 +4,515 @@
|
||||
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:fr="http://primefaces.org/freya"
|
||||
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">
|
||||
<!-- 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="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="onclick" value="PF('createClientRoleDialog').show()" />
|
||||
<ui:param name="severity" value="info" />
|
||||
</ui:include>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
FILTRES
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-filter text-blue-500"></i>
|
||||
Filtres
|
||||
</h3>
|
||||
|
||||
<h:form id="formFilters">
|
||||
<div class="grid">
|
||||
<!-- Realm -->
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="field mb-0">
|
||||
<label for="realmFilter" class="block text-900 font-medium mb-2">
|
||||
Realm
|
||||
</label>
|
||||
<p:selectOneMenu id="realmFilter"
|
||||
value="#{roleGestionBean.realmName}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Sélectionner un realm..." itemValue="" />
|
||||
<f:selectItems value="#{roleGestionBean.availableRealms}"
|
||||
var="realm"
|
||||
itemLabel="#{realm}"
|
||||
itemValue="#{realm}" />
|
||||
<p:ajax event="change"
|
||||
listener="#{roleGestionBean.loadRealmRoles}"
|
||||
update=":formRealmRoles :formClientRoles :formKpis" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Client -->
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="field mb-0">
|
||||
<label for="clientFilter" class="block text-900 font-medium mb-2">
|
||||
Client (optionnel)
|
||||
</label>
|
||||
<p:selectOneMenu id="clientFilter"
|
||||
value="#{roleGestionBean.clientName}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les clients" itemValue="" />
|
||||
<f:selectItems value="#{roleGestionBean.availableClients}"
|
||||
var="client"
|
||||
itemLabel="#{client}"
|
||||
itemValue="#{client}" />
|
||||
<p:ajax event="change"
|
||||
listener="#{roleGestionBean.loadClientRoles}"
|
||||
update=":formClientRoles" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Type -->
|
||||
<div class="col-12 md:col-4">
|
||||
<div class="field mb-0">
|
||||
<label for="typeFilter" class="block text-900 font-medium mb-2">
|
||||
Type de rôle
|
||||
</label>
|
||||
<p:selectOneMenu id="typeFilter"
|
||||
value="#{roleGestionBean.selectedTypeRole}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous les types" itemValue="" />
|
||||
<f:selectItems value="#{roleGestionBean.typeRoleOptions}"
|
||||
var="type"
|
||||
itemLabel="#{type}"
|
||||
itemValue="#{type}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
KPI CARDS
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<h:form id="formKpis">
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 border-round-lg">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Rôles Realm</div>
|
||||
<div class="text-900 font-bold text-2xl">#{roleGestionBean.realmRoles.size()}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-shield text-purple-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-globe text-600"></i>
|
||||
<span class="ml-2">Rôles du realm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 border-round-lg">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Rôles Client</div>
|
||||
<div class="text-900 font-bold text-2xl">#{roleGestionBean.clientRoles.size()}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-sitemap text-blue-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-box text-600"></i>
|
||||
<span class="ml-2">Rôles spécifiques client</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 border-round-lg">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Total Rôles</div>
|
||||
<div class="text-900 font-bold text-2xl">#{roleGestionBean.allRoles.size()}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-green-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-check-circle text-green-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-chart-bar text-600"></i>
|
||||
<span class="ml-2">Tous les rôles configurés</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="card surface-0 border-round-lg">
|
||||
<div class="flex align-items-start justify-content-between mb-3">
|
||||
<div>
|
||||
<div class="text-500 font-medium mb-1">Realm Actif</div>
|
||||
<div class="text-900 font-bold text-xl">#{roleGestionBean.realmName}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-orange-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-database text-orange-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-server text-600"></i>
|
||||
<span class="ml-2">Realm actuellement sélectionné</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
<!-- ================================================================
|
||||
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>
|
||||
|
||||
<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>
|
||||
<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">
|
||||
<p:commandButton icon="pi pi-trash"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
|
||||
title="Supprimer"
|
||||
action="#{roleGestionBean.deleteRealmRole(role.name)}"
|
||||
update=":formRealmRoles :formKpis">
|
||||
<p:confirm header="Confirmation"
|
||||
message="Voulez-vous vraiment supprimer le rôle '#{role.name}' ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
</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 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">
|
||||
<p:commandButton icon="pi pi-trash"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
|
||||
title="Supprimer"
|
||||
action="#{roleGestionBean.deleteClientRole(role.name)}"
|
||||
update=":formClientRoles :formKpis">
|
||||
<p:confirm header="Confirmation"
|
||||
message="Voulez-vous vraiment supprimer le rôle '#{role.name}' ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p: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>
|
||||
</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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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">
|
||||
<!-- ================================================================
|
||||
DIALOG CRÉATION RÔLE REALM
|
||||
================================================================ -->
|
||||
<p:dialog header="Nouveau Rôle Realm"
|
||||
widgetVar="createRealmRoleDialog"
|
||||
modal="true"
|
||||
responsive="true"
|
||||
width="600"
|
||||
showEffect="fade"
|
||||
hideEffect="fade">
|
||||
<h:form id="formCreateRealmRole">
|
||||
<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>
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<div class="field mb-3">
|
||||
<label for="realmRoleName" class="block text-900 font-medium mb-2">
|
||||
Nom du rôle <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:inputText id="realmRoleName"
|
||||
value="#{roleGestionBean.newRole.name}"
|
||||
styleClass="w-full"
|
||||
required="true"
|
||||
placeholder="ex: admin_lions">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
<f:validateRegex pattern="^[a-zA-Z0-9_-]+$" />
|
||||
</p:inputText>
|
||||
<small class="text-500">Lettres, chiffres, underscores et tirets uniquement</small>
|
||||
</div>
|
||||
|
||||
<div class="field mb-0">
|
||||
<label for="realmRoleDesc" class="block text-900 font-medium mb-2">
|
||||
Description
|
||||
</label>
|
||||
<p:inputTextarea id="realmRoleDesc"
|
||||
value="#{roleGestionBean.newRole.description}"
|
||||
styleClass="w-full"
|
||||
rows="3"
|
||||
placeholder="Description du rôle...">
|
||||
</p:inputTextarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p:messages id="messagesRealmRole" showDetail="true" closable="true" styleClass="mt-3">
|
||||
<p:autoUpdate />
|
||||
</p:messages>
|
||||
</h:form>
|
||||
|
||||
<f:facet name="footer">
|
||||
<p:commandButton value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-text"
|
||||
onclick="PF('createRealmRoleDialog').hide();"
|
||||
type="button" />
|
||||
<p:commandButton value="Créer"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success"
|
||||
action="#{roleGestionBean.createRealmRole}"
|
||||
update=":formRealmRoles :formKpis :formCreateRealmRole"
|
||||
oncomplete="if (args && !args.validationFailed) PF('createRealmRoleDialog').hide();" />
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
|
||||
<!-- 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">
|
||||
<!-- ================================================================
|
||||
DIALOG CRÉATION RÔLE CLIENT
|
||||
================================================================ -->
|
||||
<p:dialog header="Nouveau Rôle Client"
|
||||
widgetVar="createClientRoleDialog"
|
||||
modal="true"
|
||||
responsive="true"
|
||||
width="600"
|
||||
showEffect="fade"
|
||||
hideEffect="fade">
|
||||
<h:form id="formCreateClientRole">
|
||||
<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>
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<div class="field mb-3">
|
||||
<label for="clientRoleName" class="block text-900 font-medium mb-2">
|
||||
Nom du rôle <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:inputText id="clientRoleName"
|
||||
value="#{roleGestionBean.newRole.name}"
|
||||
styleClass="w-full"
|
||||
required="true"
|
||||
placeholder="ex: manager">
|
||||
<f:validateLength minimum="2" maximum="100" />
|
||||
<f:validateRegex pattern="^[a-zA-Z0-9_-]+$" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
|
||||
<div class="field mb-3">
|
||||
<label for="clientName" class="block text-900 font-medium mb-2">
|
||||
Client <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:selectOneMenu id="clientName"
|
||||
value="#{roleGestionBean.clientName}"
|
||||
styleClass="w-full"
|
||||
required="true">
|
||||
<f:selectItem itemLabel="Sélectionner un client..." itemValue="" />
|
||||
<f:selectItems value="#{roleGestionBean.availableClients}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
|
||||
<div class="field mb-0">
|
||||
<label for="clientRoleDesc" class="block text-900 font-medium mb-2">
|
||||
Description
|
||||
</label>
|
||||
<p:inputTextarea id="clientRoleDesc"
|
||||
value="#{roleGestionBean.newRole.description}"
|
||||
styleClass="w-full"
|
||||
rows="3"
|
||||
placeholder="Description du rôle...">
|
||||
</p:inputTextarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p:messages id="messagesClientRole" showDetail="true" closable="true" styleClass="mt-3">
|
||||
<p:autoUpdate />
|
||||
</p:messages>
|
||||
</h:form>
|
||||
|
||||
<f:facet name="footer">
|
||||
<p:commandButton value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-text"
|
||||
onclick="PF('createClientRoleDialog').hide();"
|
||||
type="button" />
|
||||
<p:commandButton value="Créer"
|
||||
icon="pi pi-check"
|
||||
styleClass="p-button-success"
|
||||
action="#{roleGestionBean.createClientRole}"
|
||||
update=":formClientRoles :formKpis :formCreateClientRole"
|
||||
oncomplete="if (args && !args.validationFailed) PF('createClientRoleDialog').hide();" />
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION
|
||||
================================================================ -->
|
||||
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
|
||||
responsive="true" width="400">
|
||||
<p:commandButton value="Non" type="button"
|
||||
styleClass="p-button-text"
|
||||
icon="pi pi-times" />
|
||||
<p:commandButton value="Oui" type="button"
|
||||
styleClass="p-button-danger"
|
||||
icon="pi pi-check" />
|
||||
</p:confirmDialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
<!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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{settingsBean}"/>
|
||||
<ui:define name="title">Paramètres - 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-cog text-blue-500" />
|
||||
<ui:param name="title" value="Paramètres" />
|
||||
<ui:param name="description" value="Gérer vos préférences et paramètres de compte" />
|
||||
</ui:include>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Informations du compte -->
|
||||
<div class="col-12 lg:col-8">
|
||||
<div class="card">
|
||||
<h5>Informations du compte</h5>
|
||||
<h:form id="formAccountInfo">
|
||||
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-4, col-12 md:col-8">
|
||||
<p:outputLabel for="username" value="Nom d'utilisateur" />
|
||||
<p:inputText id="username"
|
||||
value="#{userSessionBean.username}"
|
||||
readonly="true"
|
||||
styleClass="w-full" />
|
||||
|
||||
<p:outputLabel for="email" value="Email" />
|
||||
<p:inputText id="email"
|
||||
value="#{userSessionBean.email}"
|
||||
readonly="true"
|
||||
styleClass="w-full" />
|
||||
|
||||
<p:outputLabel for="fullName" value="Nom complet" />
|
||||
<p:inputText id="fullName"
|
||||
value="#{userSessionBean.fullName}"
|
||||
readonly="true"
|
||||
styleClass="w-full" />
|
||||
|
||||
<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>
|
||||
|
||||
<!-- Préférences -->
|
||||
<div class="col-12 lg:col-4">
|
||||
<div class="card">
|
||||
<h5>Préférences</h5>
|
||||
<h:form id="formPreferences">
|
||||
<div class="flex flex-column gap-3">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Thème des composants</span>
|
||||
<p:selectOneMenu value="#{guestPreferences.componentTheme}"
|
||||
styleClass="w-12rem">
|
||||
<f:selectItems value="#{guestPreferences.componentThemes}"
|
||||
var="theme"
|
||||
itemLabel="#{theme.name}"
|
||||
itemValue="#{theme.file}" />
|
||||
<p:ajax event="change" update="@form" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Mode sombre</span>
|
||||
<p:selectOneMenu value="#{guestPreferences.darkMode}"
|
||||
styleClass="w-12rem">
|
||||
<f:selectItem itemLabel="Clair" itemValue="light" />
|
||||
<f:selectItem itemLabel="Sombre" itemValue="dark" />
|
||||
<p:ajax event="change" update="@form" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<span class="text-600">Style d'input</span>
|
||||
<p:selectOneMenu value="#{guestPreferences.inputStyle}"
|
||||
styleClass="w-12rem">
|
||||
<f:selectItem itemLabel="Outlined" itemValue="outlined" />
|
||||
<f:selectItem itemLabel="Filled" itemValue="filled" />
|
||||
<p:ajax event="change" update="@form" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h5>Actions</h5>
|
||||
<div class="flex gap-2">
|
||||
<h:form>
|
||||
<p:commandButton
|
||||
value="Rafraîchir les informations"
|
||||
icon="pi pi-refresh"
|
||||
styleClass="p-button-secondary"
|
||||
action="#{userSessionBean.loadUserInfo}"
|
||||
update="formAccountInfo" />
|
||||
</h:form>
|
||||
<h:form>
|
||||
<p:commandButton
|
||||
value="Changer le mot de passe"
|
||||
icon="pi pi-key"
|
||||
styleClass="p-button-info"
|
||||
outcome="/pages/user-manager/users/profile" />
|
||||
</h:form>
|
||||
<h:form>
|
||||
<p:commandButton
|
||||
value="Sauvegarder les préférences"
|
||||
icon="pi pi-save"
|
||||
styleClass="p-button-success"
|
||||
action="#{settingsBean.savePreferences}"
|
||||
update="@form" />
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:h="jakarta.faces.html" xmlns:f="jakarta.faces.core"
|
||||
xmlns:ui="jakarta.faces.facelets" xmlns:p="http://primefaces.org/ui" template="/template.xhtml">
|
||||
|
||||
<ui:define name="title">Gestion Utilisateurs</ui:define>
|
||||
|
||||
<ui:define name="content">
|
||||
<h:form id="form">
|
||||
<div class="card">
|
||||
<p:toolbar>
|
||||
<p:toolbarGroup>
|
||||
<p:commandButton value="Nouveau" icon="pi pi-plus" actionListener="#{userView.openNew}"
|
||||
update=":dialogs:manage-user-content" oncomplete="PF('manageUserDialog').show()"
|
||||
styleClass="ui-button-success" style="margin-right: .5rem" />
|
||||
<!-- Custom CSV Download via Backend API -->
|
||||
<p:commandButton value="Tout Exporter (CSV)" icon="pi pi-download"
|
||||
actionListener="#{userView.downloadCSV}" ajax="false" styleClass="ui-button-warning ml-2" />
|
||||
</p:toolbarGroup>
|
||||
</p:toolbar>
|
||||
|
||||
<p:dataTable id="dt-users" widgetVar="dtUsers" var="user" value="#{userView.users}" reflow="true"
|
||||
styleClass="products-table" selection="#{userView.selectedUser}" rowKey="#{user.id}"
|
||||
paginator="true" rows="10" rowSelectMode="add" paginatorPosition="bottom" lazy="true">
|
||||
|
||||
<f:facet name="header">
|
||||
<div class="products-table-header">
|
||||
<span style="font-weight: bold">Utilisateurs</span>
|
||||
<span class="filter-container ui-input-icon-left" style="margin-left: 20px;">
|
||||
<i class="pi pi-search" />
|
||||
<p:inputText id="globalFilter" onkeyup="PF('dtUsers').filter()"
|
||||
placeholder="Recherche..." value="#{userView.searchTerm}">
|
||||
<p:ajax event="keyup" delay="500" listener="#{userView.search}" update="dt-users" />
|
||||
</p:inputText>
|
||||
</span>
|
||||
</div>
|
||||
</f:facet>
|
||||
|
||||
<p:column headerText="Username" sortBy="#{user.username}">
|
||||
<h:outputText value="#{user.username}" />
|
||||
</p:column>
|
||||
<p:column headerText="Email" sortBy="#{user.email}">
|
||||
<h:outputText value="#{user.email}" />
|
||||
</p:column>
|
||||
<p:column headerText="Prénom" sortBy="#{user.prenom}">
|
||||
<h:outputText value="#{user.prenom}" />
|
||||
</p:column>
|
||||
<p:column headerText="Nom" sortBy="#{user.nom}">
|
||||
<h:outputText value="#{user.nom}" />
|
||||
</p:column>
|
||||
<p:column headerText="Statut">
|
||||
<span class="product-badge status-#{user.enabled ? 'instock' : 'outofstock'}"
|
||||
style="padding: 0.25em 0.5rem; border-radius: 4px; background-color: #{user.enabled ? '#C8E6C9' : '#FFCDD2'}; color: #{user.enabled ? '#256029' : '#C63737'}; font-weight: 700;">
|
||||
#{user.enabled ? 'ACTIF' : 'INACTIF'}
|
||||
</span>
|
||||
</p:column>
|
||||
|
||||
<p:column exportable="false">
|
||||
<p:commandButton icon="pi pi-pencil" update=":dialogs:manage-user-content"
|
||||
oncomplete="PF('manageUserDialog').show()"
|
||||
styleClass="edit-button rounded-button ui-button-success" process="@this"
|
||||
style="margin-right: 5px;">
|
||||
<f:setPropertyActionListener value="#{user}" target="#{userView.selectedUser}" />
|
||||
<p:resetInput target=":dialogs:manage-user-content" />
|
||||
</p:commandButton>
|
||||
<p:commandButton class="ui-button-warning rounded-button" icon="pi pi-trash" process="@this"
|
||||
oncomplete="PF('deleteUserDialog').show()">
|
||||
<f:setPropertyActionListener value="#{user}" target="#{userView.selectedUser}" />
|
||||
</p:commandButton>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</div>
|
||||
</h:form>
|
||||
|
||||
<h:form id="dialogs">
|
||||
<p:dialog header="Détails Utilisateur" showEffect="fade" modal="true" widgetVar="manageUserDialog"
|
||||
responsive="true" width="450">
|
||||
<p:outputPanel id="manage-user-content" class="ui-fluid">
|
||||
<p:outputPanel rendered="#{not empty userView.selectedUser}">
|
||||
<div class="field" style="margin-bottom: 1rem;">
|
||||
<p:outputLabel for="username">Username</p:outputLabel>
|
||||
<p:inputText id="username" value="#{userView.selectedUser.username}" required="true"
|
||||
disabled="#{not empty userView.selectedUser.id}" />
|
||||
</div>
|
||||
<div class="field" style="margin-bottom: 1rem;">
|
||||
<p:outputLabel for="email">Email</p:outputLabel>
|
||||
<p:inputText id="email" value="#{userView.selectedUser.email}" />
|
||||
</div>
|
||||
<div class="field" style="margin-bottom: 1rem;">
|
||||
<p:outputLabel for="firstname">Prénom</p:outputLabel>
|
||||
<p:inputText id="firstname" value="#{userView.selectedUser.prenom}" />
|
||||
</div>
|
||||
<div class="field" style="margin-bottom: 1rem;">
|
||||
<p:outputLabel for="lastname">Nom</p:outputLabel>
|
||||
<p:inputText id="lastname" value="#{userView.selectedUser.nom}" />
|
||||
</div>
|
||||
<div class="field" style="margin-bottom: 1rem;">
|
||||
<p:outputLabel for="enabled" style="margin-right: 10px;">Actif</p:outputLabel>
|
||||
<p:selectBooleanCheckbox id="enabled" value="#{userView.selectedUser.enabled}" />
|
||||
</div>
|
||||
<!-- Password field only for creation -->
|
||||
<p:outputPanel rendered="#{empty userView.selectedUser.id}">
|
||||
<div class="field" style="margin-bottom: 1rem;">
|
||||
<p:outputLabel for="password">Mot de passe (Temporaire)</p:outputLabel>
|
||||
<p:password id="password" value="#{userView.selectedUser.temporaryPassword}"
|
||||
toggleMask="true" redisplay="true" />
|
||||
</div>
|
||||
</p:outputPanel>
|
||||
</p:outputPanel>
|
||||
</p:outputPanel>
|
||||
|
||||
<f:facet name="footer">
|
||||
<p:commandButton value="Sauvegarder" icon="pi pi-check" actionListener="#{userView.saveUser}"
|
||||
update="manage-user-content :form:dt-users" process="manage-user-content @this"
|
||||
oncomplete="if (!args.validationFailed) PF('manageUserDialog').hide()" />
|
||||
<p:commandButton value="Annuler" icon="pi pi-times" onclick="PF('manageUserDialog').hide()"
|
||||
class="ui-button-secondary" type="button" />
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
|
||||
<p:confirmDialog widgetVar="deleteUserDialog" showEffect="fade" width="300"
|
||||
message="Supprimer cet utilisateur ?" header="Confirmation" severity="warn">
|
||||
<p:commandButton value="Oui" icon="pi pi-check" actionListener="#{userView.deleteUser}" process="@this"
|
||||
oncomplete="PF('deleteUserDialog').hide()" update=":form:dt-users" />
|
||||
<p:commandButton value="Non" type="button" styleClass="ui-button-secondary" icon="pi pi-times"
|
||||
onclick="PF('deleteUserDialog').hide()" />
|
||||
</p:confirmDialog>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
@@ -4,32 +4,462 @@
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:fr="http://primefaces.org/freya"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{userCreationBean}"/>
|
||||
<ui:define name="title">Nouvel Utilisateur - 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-user-plus text-green-500" />
|
||||
<ui:param name="title" value="Nouvel Utilisateur" />
|
||||
<ui:param name="description" value="Créer un nouvel utilisateur dans Keycloak" />
|
||||
</ui:include>
|
||||
<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-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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<p:commandButton
|
||||
icon="pi pi-question-circle"
|
||||
styleClass="p-button-rounded p-button-text p-button-help"
|
||||
title="Aide"
|
||||
type="button"
|
||||
onclick="PF('helpDialog').show();" />
|
||||
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button p-button-text">
|
||||
<i class="pi pi-arrow-left mr-2"></i>
|
||||
Retour
|
||||
</h:link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulaire de création -->
|
||||
<div class="card">
|
||||
<ui:include src="/templates/components/user-management/user-form.xhtml">
|
||||
<ui:param name="user" value="#{userCreationBean.newUser}" />
|
||||
<ui:param name="mode" value="create" />
|
||||
<ui:param name="showRealmSelector" value="true" />
|
||||
<ui:param name="showPasswordFields" value="true" />
|
||||
<ui:param name="submitAction" value="#{userCreationBean.createUser}" />
|
||||
<ui:param name="hasSubmitAction" value="true" />
|
||||
<ui:param name="cancelOutcome" value="/pages/user-manager/users/list" />
|
||||
</ui:include>
|
||||
<!-- ================================================================
|
||||
FORMULAIRE DE CRÉATION
|
||||
================================================================ -->
|
||||
<h:form id="formUserCreation">
|
||||
<!-- Messages globaux -->
|
||||
<div class="col-12">
|
||||
<fr:growl id="formMessages" />
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
</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>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Mot de passe -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<label for="password" class="block text-900 font-medium mb-2">
|
||||
<i class="pi pi-lock text-500 mr-1"></i>
|
||||
Mot de passe <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:password id="password"
|
||||
value="#{userCreationBean.password}"
|
||||
styleClass="w-full"
|
||||
required="true"
|
||||
feedback="true"
|
||||
toggleMask="true"
|
||||
promptLabel="Entrez un mot de passe"
|
||||
weakLabel="Faible"
|
||||
goodLabel="Moyen"
|
||||
strongLabel="Fort"
|
||||
placeholder="Minimum 8 caractères">
|
||||
<f:validateLength minimum="8" maximum="100" />
|
||||
</p:password>
|
||||
<small class="text-500">
|
||||
<i class="pi pi-shield mr-1"></i>
|
||||
Au moins 8 caractères avec lettres et chiffres
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation mot de passe -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="field">
|
||||
<label for="passwordConfirm" class="block text-900 font-medium mb-2">
|
||||
<i class="pi pi-lock text-500 mr-1"></i>
|
||||
Confirmer le mot de passe <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<p:password id="passwordConfirm"
|
||||
value="#{userCreationBean.passwordConfirm}"
|
||||
styleClass="w-full"
|
||||
required="true"
|
||||
feedback="false"
|
||||
toggleMask="true"
|
||||
placeholder="Confirmer le mot de passe">
|
||||
</p:password>
|
||||
<small class="text-500">
|
||||
<i class="pi pi-info-circle mr-1"></i>
|
||||
Doit correspondre au mot de passe
|
||||
</small>
|
||||
</div>
|
||||
</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
|
||||
</label>
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex flex-column gap-3">
|
||||
<!-- Compte activé -->
|
||||
<div class="flex align-items-center">
|
||||
<p:selectBooleanCheckbox id="enabled"
|
||||
value="#{userCreationBean.newUser.enabled}">
|
||||
</p:selectBooleanCheckbox>
|
||||
<label for="enabled" class="ml-2 mb-0 cursor-pointer">
|
||||
<span class="font-semibold text-900">Compte activé</span>
|
||||
<small class="block text-500">L'utilisateur peut se connecter immédiatement</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Email vérifié -->
|
||||
<div class="flex align-items-center">
|
||||
<p:selectBooleanCheckbox id="emailVerified"
|
||||
value="#{userCreationBean.newUser.emailVerified}">
|
||||
</p:selectBooleanCheckbox>
|
||||
<label for="emailVerified" class="ml-2 mb-0 cursor-pointer">
|
||||
<span class="font-semibold text-900">Email vérifié</span>
|
||||
<small class="block text-500">Marquer l'email comme vérifié</small>
|
||||
</label>
|
||||
</div>
|
||||
</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</h5>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 Créer -->
|
||||
<fr:commandButton value="Créer l'utilisateur"
|
||||
icon="pi pi-check"
|
||||
severity="success"
|
||||
action="#{userCreationBean.createUser}"
|
||||
update=":formUserCreation"
|
||||
validateClient="true" />
|
||||
|
||||
<!-- Bouton Réinitialiser -->
|
||||
<p:commandButton value="Réinitialiser"
|
||||
icon="pi pi-refresh"
|
||||
styleClass="p-button-secondary p-button-outlined"
|
||||
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>
|
||||
|
||||
<!-- Bouton Annuler -->
|
||||
<fr:commandButton value="Annuler"
|
||||
icon="pi pi-times"
|
||||
outlined="true"
|
||||
outcome="/pages/user-manager/users/list"
|
||||
immediate="true" />
|
||||
</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:form>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION
|
||||
================================================================ -->
|
||||
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
|
||||
responsive="true" width="400">
|
||||
<p:commandButton value="Non" type="button"
|
||||
styleClass="p-button-text"
|
||||
icon="pi pi-times" />
|
||||
<p:commandButton value="Oui" type="button"
|
||||
styleClass="p-button-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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
</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>
|
||||
</f:facet>
|
||||
</p:dialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
|
||||
@@ -4,30 +4,322 @@
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:fr="http://primefaces.org/freya"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<f:metadata>
|
||||
<f:viewParam name="userId" value="#{userProfilBean.userId}" />
|
||||
<f:viewParam name="realm" value="#{userProfilBean.realmName}" />
|
||||
</f:metadata>
|
||||
|
||||
<ui:param name="page" value="#{userProfilBean}"/>
|
||||
<ui:define name="title">Modifier Utilisateur - 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-pencil text-warning-500" />
|
||||
<ui:param name="title" value="Modifier Utilisateur" />
|
||||
<ui:param name="description" value="Modifier les informations de l'utilisateur" />
|
||||
</ui:include>
|
||||
<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>
|
||||
|
||||
<!-- Formulaire d'édition -->
|
||||
<div class="card">
|
||||
<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:include>
|
||||
<!-- ================================================================
|
||||
FORMULAIRE D'ÉDITION
|
||||
================================================================ -->
|
||||
<h:form id="formUserEdit">
|
||||
<!-- Messages globaux -->
|
||||
<div class="col-12">
|
||||
<fr:growl id="formMessages" />
|
||||
</div>
|
||||
|
||||
<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+_.-]+@(.+)$" />
|
||||
</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" />
|
||||
</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" />
|
||||
</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é -->
|
||||
<div class="flex align-items-center">
|
||||
<p:selectBooleanCheckbox id="enabled"
|
||||
value="#{userProfilBean.user.enabled}">
|
||||
</p:selectBooleanCheckbox>
|
||||
<label for="enabled" class="ml-2 mb-0 cursor-pointer">
|
||||
<span class="font-semibold text-900">Compte activé</span>
|
||||
<small class="block text-500">L'utilisateur peut se connecter</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Email vérifié -->
|
||||
<div class="flex align-items-center">
|
||||
<p:selectBooleanCheckbox id="emailVerified"
|
||||
value="#{userProfilBean.user.emailVerified}">
|
||||
</p:selectBooleanCheckbox>
|
||||
<label for="emailVerified" class="ml-2 mb-0 cursor-pointer">
|
||||
<span class="font-semibold text-900">Email vérifié</span>
|
||||
<small class="block text-500">Marquer l'email comme vérifié</small>
|
||||
</label>
|
||||
</div>
|
||||
</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</h5>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION
|
||||
================================================================ -->
|
||||
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
|
||||
responsive="true" width="400">
|
||||
<p:commandButton value="Non" type="button"
|
||||
styleClass="p-button-text"
|
||||
icon="pi pi-times" />
|
||||
<p:commandButton value="Oui" type="button"
|
||||
styleClass="p-button-primary"
|
||||
icon="pi pi-check" />
|
||||
</p:confirmDialog>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
|
||||
@@ -4,117 +4,485 @@
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:fr="http://primefaces.org/freya"
|
||||
template="/templates/main-template.xhtml">
|
||||
|
||||
<ui:param name="page" value="#{userListBean}"/>
|
||||
<ui:define name="title">Liste des Utilisateurs - 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-users text-blue-500" />
|
||||
<ui:param name="title" value="Gestion des Utilisateurs" />
|
||||
<ui:param name="description" value="Gestion centralisée des utilisateurs Keycloak" />
|
||||
<ui:define name="actions">
|
||||
<h:form id="formActionsUsers">
|
||||
<div class="flex gap-2">
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Nouvel Utilisateur" />
|
||||
<ui:param name="icon" value="pi pi-user-plus" />
|
||||
<ui:param name="outcome" value="/pages/user-manager/users/create" />
|
||||
<ui:param name="severity" value="success" />
|
||||
</ui:include>
|
||||
<h:form id="formUserList">
|
||||
<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-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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<fr:commandButton
|
||||
value="Rafraîchir"
|
||||
icon="pi pi-refresh"
|
||||
severity="secondary"
|
||||
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" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
</div>
|
||||
|
||||
<!-- Statistiques KPI -->
|
||||
<div class="grid mb-4">
|
||||
<!-- Total Utilisateurs -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Total Utilisateurs" />
|
||||
<ui:param name="value" value="#{userListBean.totalRecords}" />
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
<ui:param name="subtitle" value="Utilisateurs dans le realm" />
|
||||
<ui:param name="clickable" value="true" />
|
||||
<ui:param name="clickOutcome" value="/pages/user-manager/users/list" />
|
||||
</ui:include>
|
||||
|
||||
<!-- Utilisateurs Actifs -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Utilisateurs Actifs" />
|
||||
<ui:param name="value" value="#{userListBean.activeUsersCount}" />
|
||||
<ui:param name="icon" value="pi-user-check" />
|
||||
<ui:param name="iconColor" value="green-600" />
|
||||
<ui:param name="subtitle" value="#{userListBean.activeUsersPercentage}% du total" />
|
||||
<ui:param name="progressValue" value="#{userListBean.activeUsersPercentage}" />
|
||||
<ui:param name="statusIcon" value="pi-check-circle" />
|
||||
<ui:param name="statusLabel" value="Actifs" />
|
||||
<ui:param name="statusValue" value="#{userListBean.activeUsersCount} utilisateurs" />
|
||||
<ui:param name="clickable" value="true" />
|
||||
<ui:param name="clickOutcome" value="/pages/user-manager/users/list" />
|
||||
</ui:include>
|
||||
|
||||
<!-- Utilisateurs Désactivés -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Utilisateurs Désactivés" />
|
||||
<ui:param name="value" value="#{userListBean.disabledUsersCount}" />
|
||||
<ui:param name="icon" value="pi-user-times" />
|
||||
<ui:param name="iconColor" value="red-600" />
|
||||
<ui:param name="subtitle" value="#{userListBean.disabledUsersPercentage}% du total" />
|
||||
<ui:param name="progressValue" value="#{userListBean.disabledUsersPercentage}" />
|
||||
<ui:param name="statusIcon" value="pi-times-circle" />
|
||||
<ui:param name="statusLabel" value="Désactivés" />
|
||||
<ui:param name="statusValue" value="#{userListBean.disabledUsersCount} utilisateurs" />
|
||||
<ui:param name="clickable" value="true" />
|
||||
<ui:param name="clickOutcome" value="/pages/user-manager/users/list" />
|
||||
</ui:include>
|
||||
|
||||
<!-- Realm Actuel -->
|
||||
<ui:include src="/templates/components/shared/cards/kpi-card.xhtml">
|
||||
<ui:param name="title" value="Realm Actuel" />
|
||||
<ui:param name="value" value="#{empty userListBean.realmName ? 'master' : userListBean.realmName}" />
|
||||
<ui:param name="icon" value="pi-globe" />
|
||||
<ui:param name="iconColor" value="purple-600" />
|
||||
<ui:param name="subtitle" value="Realm Keycloak" />
|
||||
<ui:param name="statusIcon" value="pi-info-circle" />
|
||||
<ui:param name="statusLabel" value="Realm" />
|
||||
<ui:param name="statusValue" value="#{empty userListBean.realmName ? 'master' : userListBean.realmName}" />
|
||||
<ui:param name="showProgress" value="false" />
|
||||
</ui:include>
|
||||
</div>
|
||||
<!-- ================================================================
|
||||
STATISTIQUES KPI (4 CARTES)
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3">Statistiques des Utilisateurs</h5>
|
||||
</div>
|
||||
|
||||
<!-- Barre de recherche et Tableau des utilisateurs dans le même formulaire -->
|
||||
<h:form id="formUsers">
|
||||
<!-- Barre de recherche -->
|
||||
<div class="card mb-3">
|
||||
<ui:include src="/templates/components/user-management/user-search-bar.xhtml">
|
||||
<ui:param name="searchCriteria" value="#{userListBean.searchCriteria}" />
|
||||
<ui:param name="searchAction" value="#{userListBean.search}" />
|
||||
<ui:param name="update" value="userTable" />
|
||||
<ui:param name="showAdvanced" value="true" />
|
||||
<ui:param name="useParentForm" value="true" />
|
||||
</ui:include>
|
||||
</div>
|
||||
<!-- KPI 1: Total Utilisateurs -->
|
||||
<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 Utilisateurs</div>
|
||||
<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">
|
||||
<i class="pi pi-users 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">Utilisateurs dans le realm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tableau des utilisateurs -->
|
||||
<div class="card">
|
||||
<ui:include src="/templates/components/shared/tables/user-data-table.xhtml">
|
||||
<ui:param name="users" value="#{userListBean.users}" />
|
||||
<ui:param name="var" value="user" />
|
||||
<ui:param name="tableId" value="userTable" />
|
||||
<ui:param name="paginator" value="true" />
|
||||
<ui:param name="rows" value="20" />
|
||||
<ui:param name="showActions" value="true" />
|
||||
<ui:param name="showRoles" value="true" />
|
||||
<ui:param name="showEmail" value="true" />
|
||||
<ui:param name="showStatus" value="true" />
|
||||
<ui:param name="update" value="userTable" />
|
||||
</ui:include>
|
||||
<!-- KPI 2: Utilisateurs Actifs -->
|
||||
<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">Utilisateurs Actifs</div>
|
||||
<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">
|
||||
<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-arrow-up text-xs"></i>
|
||||
#{userListBean.activeUsersPercentage}%
|
||||
</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" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI 3: Utilisateurs Désactivés -->
|
||||
<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">Utilisateurs Désactivés</div>
|
||||
<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">
|
||||
<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-arrow-down text-xs"></i>
|
||||
#{userListBean.disabledUsersPercentage}%
|
||||
</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" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- KPI 4: Realm Actuel -->
|
||||
<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 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>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-circle"
|
||||
style="width: 2.5rem; height: 2.5rem">
|
||||
<i class="pi pi-globe text-purple-600 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-500 text-sm">
|
||||
<i class="pi pi-server text-600"></i>
|
||||
<span class="ml-2">Realm Keycloak</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
SECTION RECHERCHE ET FILTRES
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-search text-blue-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Recherche et Filtres</h5>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<div class="field">
|
||||
<label for="searchText" class="block text-900 font-medium mb-2">
|
||||
<i class="pi pi-search text-500 mr-1"></i>
|
||||
Recherche
|
||||
</label>
|
||||
<p:inputText id="searchText"
|
||||
value="#{userListBean.searchText}"
|
||||
styleClass="w-full"
|
||||
placeholder="Nom, email...">
|
||||
<p:ajax event="keyup"
|
||||
delay="500"
|
||||
update=":formUserList:userTable"
|
||||
listener="#{userListBean.search}" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="field">
|
||||
<label for="realmFilter" class="block text-900 font-medium mb-2">
|
||||
<i class="pi pi-globe text-500 mr-1"></i>
|
||||
Realm
|
||||
</label>
|
||||
<p:selectOneMenu id="realmFilter"
|
||||
value="#{userListBean.realmName}"
|
||||
styleClass="w-full">
|
||||
<f:selectItems value="#{userListBean.availableRealms}" />
|
||||
<p:ajax event="change"
|
||||
update=":formUserList:userTable"
|
||||
listener="#{userListBean.search}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="field">
|
||||
<label for="statutFilter" class="block text-900 font-medium mb-2">
|
||||
<i class="pi pi-filter text-500 mr-1"></i>
|
||||
Statut
|
||||
</label>
|
||||
<p:selectOneMenu id="statutFilter"
|
||||
value="#{userListBean.selectedStatut}"
|
||||
styleClass="w-full">
|
||||
<f:selectItem itemLabel="Tous" itemValue="#{null}" />
|
||||
<f:selectItems value="#{userListBean.statutOptions}" />
|
||||
<p:ajax update=":formUserList:userTable"
|
||||
listener="#{userListBean.search}" />
|
||||
</p:selectOneMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 lg:col-2 flex align-items-end">
|
||||
<fr:commandButton
|
||||
value="Réinitialiser"
|
||||
icon="pi pi-refresh"
|
||||
severity="secondary"
|
||||
styleClass="w-full"
|
||||
action="#{userListBean.resetSearch}"
|
||||
update=":formUserList:userTable @form" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
TABLEAU DES UTILISATEURS
|
||||
================================================================ -->
|
||||
<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>
|
||||
|
||||
<p:growl id="formMessages" />
|
||||
|
||||
<p:dataTable
|
||||
id="userTable"
|
||||
value="#{userListBean.users}"
|
||||
var="user"
|
||||
rowKey="#{user.id}"
|
||||
paginator="true"
|
||||
rows="#{userListBean.pageSize != null ? userListBean.pageSize : 10}"
|
||||
rowsPerPageTemplate="10,20,50"
|
||||
emptyMessage="Aucun utilisateur trouvé"
|
||||
reflow="true"
|
||||
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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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: 250px; text-align: center">
|
||||
<div class="flex gap-1 justify-content-center flex-wrap">
|
||||
<!-- Bouton Voir Profil -->
|
||||
<p:button icon="pi pi-eye"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-info"
|
||||
title="Voir le profil"
|
||||
outcome="/pages/user-manager/users/view">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="realm" value="#{userListBean.realmName}" />
|
||||
</p:button>
|
||||
|
||||
<!-- Bouton Modifier -->
|
||||
<p:button icon="pi pi-pencil"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm"
|
||||
title="Modifier"
|
||||
outcome="/pages/user-manager/users/edit">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="realm" value="#{userListBean.realmName}" />
|
||||
</p:button>
|
||||
|
||||
<!-- Bouton Gérer les Rôles -->
|
||||
<p:button icon="pi pi-key"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-help"
|
||||
title="Gérer les rôles"
|
||||
outcome="/pages/user-manager/roles/assign">
|
||||
<f:param name="userId" value="#{user.id}" />
|
||||
<f:param name="realm" value="#{userListBean.realmName}" />
|
||||
</p:button>
|
||||
|
||||
<!-- Bouton Désactiver (si actif) -->
|
||||
<p:commandButton icon="pi pi-ban"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-warning"
|
||||
title="Désactiver"
|
||||
action="#{userListBean.deactivateUserAction}"
|
||||
update=":formUserList:userTable :formUserList:formMessages"
|
||||
process="@this"
|
||||
rendered="#{user.enabled}">
|
||||
<f:attribute name="userId" value="#{user.id}" />
|
||||
<p:confirm header="Désactiver l'utilisateur"
|
||||
message="Voulez-vous vraiment désactiver l'utilisateur #{user.username} ?"
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
|
||||
<!-- Bouton Activer (si inactif) -->
|
||||
<fr:commandButton icon="pi pi-check"
|
||||
rounded="true"
|
||||
text="true"
|
||||
size="small"
|
||||
severity="success"
|
||||
title="Activer"
|
||||
action="#{userListBean.activateUserAction}"
|
||||
update=":formUserList:userTable :formUserList:formMessages"
|
||||
process="@this"
|
||||
rendered="#{not user.enabled}">
|
||||
<f:attribute name="userId" value="#{user.id}" />
|
||||
</fr:commandButton>
|
||||
|
||||
<!-- Bouton Supprimer -->
|
||||
<p:commandButton icon="pi pi-trash"
|
||||
styleClass="p-button-rounded p-button-text p-button-sm p-button-danger"
|
||||
title="Supprimer"
|
||||
action="#{userListBean.deleteUserAction}"
|
||||
update=":formUserList:userTable :formUserList:formMessages"
|
||||
process="@this">
|
||||
<f:attribute name="userId" value="#{user.id}" />
|
||||
<p:confirm header="Supprimer l'utilisateur"
|
||||
message="Voulez-vous vraiment supprimer définitivement l'utilisateur #{user.username} ? Cette action est irréversible."
|
||||
icon="pi pi-exclamation-triangle" />
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
ACTIONS RAPIDES
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center gap-2 mb-3">
|
||||
<i class="pi pi-bolt text-orange-500" style="font-size: 1.5rem"></i>
|
||||
<h5 class="m-0">Actions Rapides</h5>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<fr:commandButton
|
||||
value="Créer un Utilisateur"
|
||||
icon="pi pi-user-plus"
|
||||
severity="success"
|
||||
styleClass="w-full"
|
||||
outcome="/pages/user-manager/users/create" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<fr:commandButton
|
||||
value="Exporter la Liste"
|
||||
icon="pi pi-download"
|
||||
severity="secondary"
|
||||
styleClass="w-full"
|
||||
action="#{userListBean.exportToCSV}"
|
||||
ajax="false" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<fr:commandButton
|
||||
value="Importer des Utilisateurs"
|
||||
icon="pi pi-upload"
|
||||
severity="info"
|
||||
styleClass="w-full"
|
||||
onclick="PF('importUsersDialog').show()"
|
||||
type="button" />
|
||||
</div>
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<fr:commandButton
|
||||
value="Gestion des Rôles"
|
||||
icon="pi pi-shield"
|
||||
severity="primary"
|
||||
styleClass="w-full"
|
||||
outcome="/pages/user-manager/roles/list" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG D'IMPORT
|
||||
================================================================ -->
|
||||
<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">
|
||||
<fr:commandButton value="Annuler"
|
||||
icon="pi pi-times"
|
||||
severity="secondary"
|
||||
onclick="PF('importUsersDialog').hide()"
|
||||
type="button" />
|
||||
<fr:commandButton value="Importer"
|
||||
icon="pi pi-upload"
|
||||
severity="success"
|
||||
action="#{userListBean.importUsers}"
|
||||
update=":formUserList"
|
||||
oncomplete="PF('importUsersDialog').hide()" />
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
@@ -4,104 +4,418 @@
|
||||
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="#{userProfilBean}"/>
|
||||
<ui:define name="title">Profil Utilisateur - Lions User Manager</ui:define>
|
||||
<ui:param name="page" value="#{userSessionBean}"/>
|
||||
<ui:define name="title">Mon Profil - 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-user text-blue-500" />
|
||||
<ui:param name="title" value="Profil Utilisateur" />
|
||||
<ui:param name="description" value="Détails et gestion de l'utilisateur" />
|
||||
<ui:define name="actions">
|
||||
<h:form id="formActionsProfile">
|
||||
<div class="flex gap-2">
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Modifier" />
|
||||
<ui:param name="icon" value="pi pi-pencil" />
|
||||
<ui:param name="action" value="#{userProfilBean.enableEditMode}" />
|
||||
<ui:param name="update" value="userProfileForm" />
|
||||
<ui:param name="severity" value="warning" />
|
||||
<ui:param name="rendered" value="#{not userProfilBean.editMode}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Retour" />
|
||||
<ui:param name="icon" value="pi pi-arrow-left" />
|
||||
<ui:param name="outcome" value="/pages/user-manager/users/list" />
|
||||
<ui:param name="severity" value="secondary" />
|
||||
</ui:include>
|
||||
</div>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Carte utilisateur -->
|
||||
<div class="col-12 md:col-4">
|
||||
<ui:include src="/templates/components/user-management/user-card.xhtml">
|
||||
<ui:param name="user" value="#{userProfilBean.user}" />
|
||||
<ui:param name="showActions" value="false" />
|
||||
<ui:param name="showRoles" value="true" />
|
||||
</ui:include>
|
||||
<!-- ================================================================
|
||||
EN-TÊTE DE LA PAGE
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="flex align-items-center justify-content-between">
|
||||
<h2 class="text-900 font-semibold text-xl m-0">
|
||||
<i class="pi pi-user text-blue-500 mr-2"></i>
|
||||
Mon Profil
|
||||
</h2>
|
||||
<h:link outcome="/pages/user-manager/dashboard" styleClass="p-button p-button-text">
|
||||
<i class="pi pi-arrow-left mr-2"></i>
|
||||
Retour au tableau de bord
|
||||
</h:link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulaire d'édition -->
|
||||
<div class="col-12 md:col-8">
|
||||
<!-- ================================================================
|
||||
CARTE PROFIL PRINCIPAL
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h:form id="userProfileForm">
|
||||
<c:choose>
|
||||
<c:when test="#{userProfilBean.editMode}">
|
||||
<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="" />
|
||||
</ui:include>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<!-- Mode lecture seule -->
|
||||
<ui:include src="/templates/components/user-management/user-form.xhtml">
|
||||
<ui:param name="user" value="#{userProfilBean.user}" />
|
||||
<ui:param name="readonly" value="true" />
|
||||
</ui:include>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</h:form>
|
||||
</div>
|
||||
<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);">
|
||||
#{userSessionBean.initials}
|
||||
</div>
|
||||
|
||||
<!-- Actions rapides -->
|
||||
<div class="card mt-3">
|
||||
<h5>Actions Rapides</h5>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Réinitialiser mot de passe" />
|
||||
<ui:param name="icon" value="pi pi-key" />
|
||||
<ui:param name="onclick" value="PF('resetPasswordDialog').show()" />
|
||||
<ui:param name="severity" value="info" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="#{userProfilBean.user.enabled ? 'Désactiver' : 'Activer'}" />
|
||||
<ui:param name="icon" value="pi #{userProfilBean.user.enabled ? 'pi-times' : 'pi-check'}" />
|
||||
<ui:param name="action" value="#{userProfilBean.user.enabled ? userProfilBean.deactivateUser() : userProfilBean.activateUser()}" />
|
||||
<ui:param name="update" value="userProfileForm" />
|
||||
<ui:param name="severity" value="#{userProfilBean.user.enabled ? 'warning' : 'success'}" />
|
||||
</ui:include>
|
||||
<ui:include src="/templates/components/shared/buttons/button-user-action.xhtml">
|
||||
<ui:param name="value" value="Déconnecter toutes les sessions" />
|
||||
<ui:param name="icon" value="pi pi-sign-out" />
|
||||
<ui:param name="action" value="#{userProfilBean.logoutAllSessions}" />
|
||||
<ui:param name="update" value="userProfileForm" />
|
||||
<ui:param name="severity" value="info" />
|
||||
</ui:include>
|
||||
<!-- Nom complet -->
|
||||
<h3 class="text-900 font-semibold text-2xl mb-2">#{userSessionBean.fullName}</h3>
|
||||
|
||||
<!-- Email -->
|
||||
<p class="text-600 mb-3 flex align-items-center justify-content-center gap-2">
|
||||
<i class="pi pi-envelope"></i>
|
||||
#{userSessionBean.email}
|
||||
</p>
|
||||
|
||||
<!-- Badge de statut -->
|
||||
<div class="inline-flex align-items-center justify-content-center gap-2">
|
||||
<span class="inline-flex align-items-center gap-2 bg-green-100 text-green-700 px-3 py-2 border-round font-semibold" style="font-size: 1rem;">
|
||||
<i class="pi pi-circle-fill" style="font-size: 0.5rem; animation: pulse 2s ease-in-out infinite;"></i>
|
||||
<span>Connecté</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Badge du rôle principal -->
|
||||
<div class="mt-3 flex justify-content-center">
|
||||
<span class="inline-flex align-items-center bg-blue-100 text-blue-700 px-3 py-1 border-round font-semibold text-sm" style="text-transform: uppercase; letter-spacing: 0.5px;">
|
||||
#{userSessionBean.primaryRole}
|
||||
</span>
|
||||
</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">#{userSessionBean.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">#{userSessionBean.fullName}</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">#{userSessionBean.email}</p>
|
||||
<i class="pi pi-check-circle text-green-500" 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">#{userSessionBean.firstName}</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">#{userSessionBean.lastName}</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 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" />
|
||||
</ui:repeat>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 pb-3 border-bottom-1 surface-border">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Niveau d'accès</label>
|
||||
<p class="text-900 font-semibold m-0">
|
||||
<h:outputText value="Administrateur système" rendered="#{userSessionBean.hasRole('admin')}" />
|
||||
<h:outputText value="Gestionnaire utilisateurs" rendered="#{userSessionBean.hasRole('user_manager') and not userSessionBean.hasRole('admin')}" />
|
||||
<h:outputText value="Consultation utilisateurs" rendered="#{userSessionBean.hasRole('user_viewer') and not userSessionBean.hasRole('user_manager') and not userSessionBean.hasRole('admin')}" />
|
||||
<h:outputText value="Auditeur" rendered="#{userSessionBean.hasRole('auditor') and not userSessionBean.hasRole('user_viewer') and not userSessionBean.hasRole('user_manager') and not userSessionBean.hasRole('admin')}" />
|
||||
<h:outputText value="Utilisateur standard" rendered="#{not userSessionBean.hasRole('admin') and not userSessionBean.hasRole('user_manager') and not userSessionBean.hasRole('user_viewer') and not userSessionBean.hasRole('auditor')}" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
INFORMATIONS DE SESSION OIDC
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-shield text-orange-500"></i>
|
||||
Informations de Session OIDC
|
||||
</h3>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Colonne gauche: Token Information -->
|
||||
<div class="col-12 md:col-6">
|
||||
<h4 class="text-900 font-semibold mb-3">Informations du Token</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Issuer (Émetteur)</label>
|
||||
<p class="text-700 m-0 text-sm font-mono bg-bluegray-50 p-2 border-round">
|
||||
#{userSessionBean.issuer}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Subject (Identifiant)</label>
|
||||
<p class="text-700 m-0 text-sm font-mono bg-bluegray-50 p-2 border-round">
|
||||
#{userSessionBean.subject}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Audience</label>
|
||||
<p class="text-700 m-0 text-sm font-mono bg-bluegray-50 p-2 border-round">
|
||||
account
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Colonne droite: Session Details -->
|
||||
<div class="col-12 md:col-6">
|
||||
<h4 class="text-900 font-semibold mb-3">Détails de la Session</h4>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Expiration du token</label>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-calendar text-orange-500"></i>
|
||||
<p class="text-700 m-0 text-sm">
|
||||
<h:outputText value="#{userSessionBean.expirationTime}">
|
||||
<f:convertDateTime pattern="dd/MM/yyyy à HH:mm:ss" timeZone="Europe/Paris" type="both"/>
|
||||
</h:outputText>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Émis le</label>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-clock text-blue-500"></i>
|
||||
<p class="text-700 m-0 text-sm">
|
||||
<h:outputText value="#{userSessionBean.issuedAt}">
|
||||
<f:convertDateTime pattern="dd/MM/yyyy à HH:mm:ss" timeZone="Europe/Paris" type="both"/>
|
||||
</h:outputText>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Realm Keycloak</label>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-globe text-purple-500"></i>
|
||||
<p class="text-700 m-0 text-sm font-semibold">
|
||||
lions-user-manager
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-600 font-medium mb-1 text-sm">Durée de validité</label>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-hourglass text-green-500"></i>
|
||||
<p class="text-700 m-0 text-sm">
|
||||
Session active
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
STATISTIQUES D'ACTIVITÉ
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-chart-line text-green-500"></i>
|
||||
Statistiques d'Activité
|
||||
</h3>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-2">
|
||||
<span class="text-600 font-medium text-sm">Connexions</span>
|
||||
<i class="pi pi-sign-in text-blue-500"></i>
|
||||
</div>
|
||||
<p class="text-900 font-bold text-2xl m-0">--</p>
|
||||
<small class="text-500">Total des connexions</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-2">
|
||||
<span class="text-600 font-medium text-sm">Dernière connexion</span>
|
||||
<i class="pi pi-clock text-green-500"></i>
|
||||
</div>
|
||||
<p class="text-900 font-bold text-xl m-0">Aujourd'hui</p>
|
||||
<small class="text-500">Session en cours</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-2">
|
||||
<span class="text-600 font-medium text-sm">Actions</span>
|
||||
<i class="pi pi-history text-orange-500"></i>
|
||||
</div>
|
||||
<p class="text-900 font-bold text-2xl m-0">--</p>
|
||||
<small class="text-500">Actions effectuées</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-3">
|
||||
<div class="surface-50 border-round p-3">
|
||||
<div class="flex align-items-center justify-content-between mb-2">
|
||||
<span class="text-600 font-medium text-sm">Sessions</span>
|
||||
<i class="pi pi-desktop text-purple-500"></i>
|
||||
</div>
|
||||
<p class="text-900 font-bold text-2xl m-0">1</p>
|
||||
<small class="text-500">Session active</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
ACTIONS
|
||||
================================================================ -->
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<h3 class="text-900 font-semibold text-lg mb-4 flex align-items-center gap-2">
|
||||
<i class="pi pi-cog text-gray-500"></i>
|
||||
Actions Rapides
|
||||
</h3>
|
||||
|
||||
<h:form id="formProfileActions">
|
||||
<div class="grid">
|
||||
<!-- Gestion du Profil -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
|
||||
<i class="pi pi-user text-blue-500"></i>
|
||||
<span>Gestion du Profil</span>
|
||||
</h4>
|
||||
<div class="flex flex-column gap-2">
|
||||
<p:commandButton value="Modifier mon profil"
|
||||
icon="pi pi-pencil"
|
||||
styleClass="p-button-outlined w-full justify-content-start"
|
||||
disabled="true">
|
||||
<f:attribute name="data-tooltip" value="Fonctionnalité gérée par Keycloak"/>
|
||||
</p:commandButton>
|
||||
|
||||
<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"/>
|
||||
</p:commandButton>
|
||||
|
||||
<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"/>
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gestion des Sessions -->
|
||||
<div class="col-12 md:col-6">
|
||||
<div class="surface-50 border-round p-3 h-full">
|
||||
<h4 class="text-900 font-semibold mb-3 flex align-items-center gap-2">
|
||||
<i class="pi pi-desktop text-purple-500"></i>
|
||||
<span>Sessions et Sécurité</span>
|
||||
</h4>
|
||||
<div class="flex flex-column gap-2">
|
||||
<p:commandButton value="Voir mes sessions actives"
|
||||
icon="pi pi-desktop"
|
||||
styleClass="p-button-outlined p-button-info w-full justify-content-start"
|
||||
disabled="true">
|
||||
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
|
||||
</p:commandButton>
|
||||
|
||||
<p:commandButton value="Historique des connexions"
|
||||
icon="pi pi-history"
|
||||
styleClass="p-button-outlined p-button-secondary w-full justify-content-start"
|
||||
disabled="true">
|
||||
<f:attribute name="data-tooltip" value="Fonctionnalité à venir"/>
|
||||
</p:commandButton>
|
||||
|
||||
<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" />
|
||||
</p:commandButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
DIALOG DE CONFIRMATION
|
||||
================================================================ -->
|
||||
<p:confirmDialog global="true" showEffect="fade" hideEffect="fade"
|
||||
responsive="true" width="400">
|
||||
<p:commandButton value="Non" type="button"
|
||||
styleClass="p-button-text"
|
||||
icon="pi pi-times" />
|
||||
<p:commandButton value="Oui" type="button"
|
||||
styleClass="p-button-danger"
|
||||
icon="pi pi-check" />
|
||||
</p:confirmDialog>
|
||||
|
||||
<!-- Animation CSS pour le badge "Connecté" -->
|
||||
<style>
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
</style>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
<!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">
|
||||
|
||||
<f:metadata>
|
||||
<f:viewParam name="userId" value="#{userProfilBean.userId}" />
|
||||
<f:viewParam name="realm" value="#{userProfilBean.realmName}" />
|
||||
</f:metadata>
|
||||
|
||||
<ui:param name="page" value="#{userProfilBean}"/>
|
||||
<ui:define name="title">Profil 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-user text-blue-500" style="font-size: 2rem"></i>
|
||||
<div>
|
||||
<h3 class="m-0 mb-1">Profil de l'Utilisateur</h3>
|
||||
<p class="text-600 m-0">Détails et informations de l'utilisateur</p>
|
||||
</div>
|
||||
</div>
|
||||
<h:link outcome="/pages/user-manager/users/list" styleClass="p-button p-button-text">
|
||||
<i class="pi pi-arrow-left mr-2"></i>
|
||||
Retour à la liste
|
||||
</h:link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ================================================================
|
||||
CARTE PROFIL PRINCIPAL
|
||||
================================================================ -->
|
||||
<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>
|
||||
|
||||
<!-- 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">
|
||||
<p:badge value="#{role}" severity="info" styleClass="text-sm"></p:badge>
|
||||
</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">
|
||||
<p:tag value="#{userProfilBean.user.enabled ? 'ACTIF' : 'INACTIF'}"
|
||||
severity="#{userProfilBean.user.enabled ? 'success' : 'danger'}"
|
||||
styleClass="text-sm"></p:tag>
|
||||
</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}">
|
||||
<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>
|
||||
</h:panelGroup>
|
||||
</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/roles/assign">
|
||||
<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>
|
||||
</div>
|
||||
</ui:define>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:composite="http://xmlns.jcp.org/jsf/composite"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
|
||||
|
||||
<composite:interface>
|
||||
<composite:attribute name="userId" type="java.lang.String" required="true"/>
|
||||
<composite:attribute name="userEnabled" type="java.lang.Boolean" required="false" default="true"/>
|
||||
<composite:attribute name="update" type="java.lang.String" required="false" default="@form"/>
|
||||
<composite:attribute name="showView" type="java.lang.Boolean" required="false" default="true"/>
|
||||
<composite:attribute name="showEdit" type="java.lang.Boolean" required="false" default="true"/>
|
||||
<composite:attribute name="showDelete" type="java.lang.Boolean" required="false" default="true"/>
|
||||
<composite:attribute name="showActivate" type="java.lang.Boolean" required="false" default="true"/>
|
||||
<composite:attribute name="showDeactivate" type="java.lang.Boolean" required="false" default="true"/>
|
||||
<composite:attribute name="showResetPassword" type="java.lang.Boolean" required="false" default="true"/>
|
||||
<composite:attribute name="viewPage" type="java.lang.String" required="false" default="/pages/user-manager/users/profile"/>
|
||||
<composite:attribute name="editPage" type="java.lang.String" required="false" default="/pages/user-manager/users/edit"/>
|
||||
<composite:attribute name="activateAction"
|
||||
method-signature="void activateAction(jakarta.faces.event.ActionEvent)"
|
||||
required="false"/>
|
||||
<composite:attribute name="deactivateAction"
|
||||
method-signature="void deactivateAction(jakarta.faces.event.ActionEvent)"
|
||||
required="false"/>
|
||||
<composite:attribute name="deleteAction"
|
||||
method-signature="void deleteAction(jakarta.faces.event.ActionEvent)"
|
||||
required="false"/>
|
||||
</composite:interface>
|
||||
|
||||
<composite:implementation>
|
||||
<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:menu styleClass="w-12rem">
|
||||
<c:if test="#{cc.attrs.showView}">
|
||||
<p:menuitem
|
||||
value="Voir le profil"
|
||||
icon="pi pi-eye"
|
||||
outcome="#{cc.attrs.viewPage}">
|
||||
<f:param name="userId" value="#{cc.attrs.userId}" />
|
||||
</p:menuitem>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{cc.attrs.showEdit}">
|
||||
<p:menuitem
|
||||
value="Modifier"
|
||||
icon="pi pi-pencil"
|
||||
outcome="#{cc.attrs.editPage}">
|
||||
<f:param name="userId" value="#{cc.attrs.userId}" />
|
||||
</p:menuitem>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{cc.attrs.showResetPassword}">
|
||||
<p:menuitem
|
||||
value="Réinitialiser mot de passe"
|
||||
icon="pi pi-key"
|
||||
onclick="PF('resetPasswordDialog').show()" />
|
||||
</c:if>
|
||||
|
||||
<p:separator />
|
||||
|
||||
<c:if test="#{cc.attrs.showActivate and !cc.attrs.userEnabled}">
|
||||
<c:if test="#{not empty cc.attrs.activateAction}">
|
||||
<p:menuitem
|
||||
value="Activer"
|
||||
icon="pi pi-check"
|
||||
styleClass="text-green-600"
|
||||
actionListener="#{cc.attrs.activateAction}">
|
||||
<f:attribute name="userId" value="#{cc.attrs.userId}" />
|
||||
<p:ajax update="#{cc.attrs.update}" />
|
||||
</p:menuitem>
|
||||
</c:if>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{cc.attrs.showDeactivate and cc.attrs.userEnabled}">
|
||||
<c:if test="#{not empty cc.attrs.deactivateAction}">
|
||||
<p:menuitem
|
||||
value="Désactiver"
|
||||
icon="pi pi-times"
|
||||
styleClass="text-orange-600"
|
||||
actionListener="#{cc.attrs.deactivateAction}">
|
||||
<f:attribute name="userId" value="#{cc.attrs.userId}" />
|
||||
<p:ajax update="#{cc.attrs.update}" />
|
||||
</p:menuitem>
|
||||
</c:if>
|
||||
</c:if>
|
||||
|
||||
<c:if test="#{cc.attrs.showDelete}">
|
||||
<c:if test="#{not empty cc.attrs.deleteAction}">
|
||||
<p:separator />
|
||||
<p:menuitem
|
||||
value="Supprimer"
|
||||
icon="pi pi-trash"
|
||||
styleClass="text-red-600"
|
||||
actionListener="#{cc.attrs.deleteAction}">
|
||||
<f:attribute name="userId" value="#{cc.attrs.userId}" />
|
||||
<p:ajax update="#{cc.attrs.update}" />
|
||||
</p:menuitem>
|
||||
</c:if>
|
||||
</c:if>
|
||||
</p:menu>
|
||||
</p:commandButton>
|
||||
</composite:implementation>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,625 @@
|
||||
/* ============================================================================
|
||||
Lions User Manager - Enhanced Custom Topbar Styles
|
||||
|
||||
Auteur: Lions User Manager
|
||||
Version: 2.0.0
|
||||
Description: Styles améliorés pour la topbar avec intégration intelligente
|
||||
des patterns Freya layout pour un rendu parfait
|
||||
|
||||
Intégrations:
|
||||
- Freya Layout Variables & Patterns
|
||||
- Support Dark/Light Theme
|
||||
- Animations fluides (fadeInDown, modal-in)
|
||||
- PrimeFlex utility classes
|
||||
- Responsive design
|
||||
============================================================================ */
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
BASE TOPBAR LAYOUT OVERRIDES
|
||||
Améliore la structure de base de la topbar Freya
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
.layout-topbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
width: 100%;
|
||||
height: 62px;
|
||||
transition: width 0.2s, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.layout-topbar .layout-topbar-wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.layout-topbar .layout-topbar-wrapper .layout-topbar-right {
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
padding: 0 16px 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.layout-topbar .layout-topbar-wrapper .layout-topbar-right .layout-topbar-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-grow: 1;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.layout-topbar .layout-topbar-wrapper .layout-topbar-right .layout-topbar-actions > li {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
USER PROFILE LINK - Enhanced with Freya patterns
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
.layout-topbar .user-profile-link {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-topbar .user-profile-link::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05));
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.layout-topbar .user-profile-link:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.layout-topbar .user-profile-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* User Avatar - Integration with Freya avatar patterns */
|
||||
.layout-topbar .user-profile-link .user-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.layout-topbar .user-profile-link:hover .user-avatar {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* User Info Container */
|
||||
.layout-topbar .user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
line-height: 1.2;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* User Name - Enhanced typography */
|
||||
.layout-topbar .user-name {
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 0.125rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
/* User Email */
|
||||
.layout-topbar .user-email {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.85;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
/* User Role Badge */
|
||||
.layout-topbar .user-role {
|
||||
font-size: 0.7rem;
|
||||
color: var(--primary-color);
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
background: rgba(var(--primary-color-rgb, 79, 142, 236), 0.1);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.layout-topbar .user-separator {
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.5;
|
||||
font-weight: 300;
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
/* Online Status Indicator */
|
||||
.layout-topbar .user-status {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.layout-topbar .user-status.online {
|
||||
background-color: #4CAF50;
|
||||
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
|
||||
animation: pulse-online 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-online {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 4px rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
USER DROPDOWN MENU - Enhanced with Freya dropdown patterns
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
.layout-topbar .user-dropdown-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 62px;
|
||||
right: 0;
|
||||
min-width: 280px;
|
||||
max-width: 320px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid var(--surface-border);
|
||||
background: var(--surface-card);
|
||||
overflow: hidden;
|
||||
z-index: 1000;
|
||||
animation-duration: 0.2s;
|
||||
animation-timing-function: cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/* Show dropdown when parent is active */
|
||||
.layout-topbar .user-profile.active-topmenuitem > .user-dropdown-menu {
|
||||
display: block;
|
||||
animation-name: fadeInDown;
|
||||
}
|
||||
|
||||
/* Dropdown Header - Integration with Freya gradient patterns */
|
||||
.user-dropdown-header {
|
||||
padding: 1.25rem 1rem;
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--primary-600, #387FE9));
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-dropdown-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
right: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
|
||||
animation: shimmer 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% { transform: translate(0, 0); }
|
||||
50% { transform: translate(-20%, -20%); }
|
||||
}
|
||||
|
||||
/* Dropdown Avatar */
|
||||
.user-dropdown-avatar {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-dropdown-avatar > div {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.user-status-indicator {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.user-status-indicator.online {
|
||||
background-color: #4CAF50;
|
||||
animation: pulse-indicator 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-indicator {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
/* Dropdown User Info */
|
||||
.user-dropdown-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-dropdown-name {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.user-dropdown-email {
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.95;
|
||||
margin-bottom: 0.25rem;
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.user-dropdown-role {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
display: inline-block;
|
||||
color: white;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* Dividers */
|
||||
.user-dropdown-divider {
|
||||
height: 1px;
|
||||
background-color: var(--surface-border);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Menu Sections */
|
||||
.user-dropdown-section {
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--text-color-secondary);
|
||||
padding: 0 1rem 0.5rem 1rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.section-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Dropdown Items - Enhanced with Freya interaction patterns */
|
||||
.dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s cubic-bezier(0.05, 0.74, 0.2, 0.99);
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dropdown-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background: var(--primary-color);
|
||||
transform: scaleX(0);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.dropdown-item:hover::before {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: var(--surface-hover);
|
||||
color: var(--primary-color);
|
||||
padding-left: 1.25rem;
|
||||
}
|
||||
|
||||
.dropdown-item:active {
|
||||
background-color: var(--surface-ground);
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.dropdown-item i {
|
||||
width: 1.25rem;
|
||||
text-align: center;
|
||||
color: var(--text-color-secondary);
|
||||
transition: all 0.2s ease;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.dropdown-item:hover i {
|
||||
color: var(--primary-color);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.dropdown-item span {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-arrow {
|
||||
margin-left: auto;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.dropdown-item:hover .item-arrow {
|
||||
opacity: 1;
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
/* Logout Item - Enhanced danger state */
|
||||
.logout-item {
|
||||
color: var(--red-500) !important;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.logout-item:hover {
|
||||
background-color: var(--red-50) !important;
|
||||
color: var(--red-600) !important;
|
||||
}
|
||||
|
||||
.logout-item i {
|
||||
color: var(--red-500) !important;
|
||||
}
|
||||
|
||||
.logout-item:hover i {
|
||||
color: var(--red-600) !important;
|
||||
transform: scale(1.1) rotate(-5deg);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
ANIMATIONS - Integration with Freya animation patterns
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
@keyframes dropdownFadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px) scale(0.95);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.user-dropdown-menu {
|
||||
animation: dropdownFadeIn 0.3s ease-out;
|
||||
transform-origin: top right;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
DARK MODE SUPPORT - Integration with Freya dark theme
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
.layout-wrapper.layout-topbar-dark .layout-topbar {
|
||||
background-color: #293241;
|
||||
box-shadow: 0 10px 40px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-topbar-dark .user-profile-link:hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-topbar-dark .user-dropdown-menu {
|
||||
background: var(--surface-900, #1E1E1E);
|
||||
border-color: var(--surface-700, #383838);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-topbar-dark .user-dropdown-divider {
|
||||
background-color: var(--surface-700, #383838);
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-topbar-dark .section-title {
|
||||
color: var(--text-color-secondary);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-topbar-dark .dropdown-item {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-topbar-dark .dropdown-item:hover {
|
||||
background-color: var(--surface-800, #2A2A2A);
|
||||
}
|
||||
|
||||
.layout-wrapper.layout-topbar-dark .logout-item:hover {
|
||||
background-color: rgba(211, 47, 47, 0.1) !important;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
RESPONSIVE DESIGN - Integration with Freya responsive patterns
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.layout-topbar .user-dropdown-menu {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
position: fixed;
|
||||
top: 62px;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.layout-topbar .user-dropdown-menu {
|
||||
min-width: 260px;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.user-dropdown-header {
|
||||
padding: 1rem 0.75rem;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
padding: 0.625rem 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
padding: 0 0.75rem 0.5rem 0.75rem;
|
||||
}
|
||||
|
||||
/* Hide user info on mobile */
|
||||
.layout-topbar .user-info {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-topbar .user-profile-link {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.layout-topbar .user-dropdown-menu {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
ACCESSIBILITY ENHANCEMENTS
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
.dropdown-item:focus,
|
||||
.user-profile-link:focus {
|
||||
outline: 2px solid var(--primary-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.layout-topbar .user-profile-link,
|
||||
.dropdown-item,
|
||||
.user-dropdown-menu,
|
||||
.user-avatar,
|
||||
.item-arrow {
|
||||
animation: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
UTILITY CLASSES - PrimeFlex integration
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
.layout-topbar .flex {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.layout-topbar .align-items-center {
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.layout-topbar .justify-content-center {
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.layout-topbar .gap-2 {
|
||||
gap: 0.5rem !important;
|
||||
}
|
||||
|
||||
.layout-topbar .text-white {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.layout-topbar .border-circle {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
||||
.layout-topbar .bg-primary {
|
||||
background-color: var(--primary-color) !important;
|
||||
}
|
||||
@@ -0,0 +1,795 @@
|
||||
/*
|
||||
* ╔════════════════════════════════════════════════════════════╗
|
||||
* ║ Lions Platform Elite Topbar Styles (Freya Design System) ║
|
||||
* ║ Modern, Professional, Responsive ║
|
||||
* ╚════════════════════════════════════════════════════════════╝
|
||||
*/
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
/* BASE TOPBAR */
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.unionflow-elite,
|
||||
.lions-elite {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-600) 100%);
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
||||
position: relative;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.unionflow-elite::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
var(--primary-300) 50%,
|
||||
transparent 100%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* App Version Badge */
|
||||
.app-version {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0.625rem;
|
||||
background: rgba(255,255,255,0.15);
|
||||
border-radius: 12px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255,255,255,0.9);
|
||||
margin-left: 0.75rem;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
/* SEARCH */
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.search-item .topbar-icon {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.search-item:hover .topbar-icon {
|
||||
transform: scale(1.1);
|
||||
color: var(--primary-100);
|
||||
}
|
||||
|
||||
.search-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.5rem);
|
||||
right: 0;
|
||||
min-width: 400px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
|
||||
padding: 1rem;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-10px);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.search-item:hover .search-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.search-wrapper-elite {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
background: var(--surface-50);
|
||||
border-radius: 8px;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--surface-border);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.search-wrapper-elite:focus-within {
|
||||
background: white;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(var(--primary-rgb), 0.1);
|
||||
}
|
||||
|
||||
.search-wrapper-elite .pi-search {
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.search-wrapper-elite .search-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0.5rem 0;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.search-wrapper-elite .search-input:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
/* NOTIFICATIONS */
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.notifications-item {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.badge-count {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
color: white;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 700;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 10px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.4);
|
||||
animation: pulse-badge 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-badge {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
.notifications-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.5rem);
|
||||
right: 0;
|
||||
min-width: 360px;
|
||||
max-width: 400px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-10px);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 1px solid var(--surface-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notifications-item:hover .notifications-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.notif-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 1.25rem;
|
||||
background: var(--surface-50);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.count-label {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.notif-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.875rem;
|
||||
padding: 0.875rem 1.25rem;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.notif-item:hover {
|
||||
background: var(--surface-50);
|
||||
}
|
||||
|
||||
.notif-item i {
|
||||
font-size: 1.25rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.notif-title {
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
font-size: 0.875rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.notif-time {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.notif-footer {
|
||||
padding: 0.75rem 1.25rem;
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--surface-border);
|
||||
background: var(--surface-50);
|
||||
}
|
||||
|
||||
.notif-footer a {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.notif-footer a:hover {
|
||||
color: var(--primary-600);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
/* USER PROFILE */
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.elite-user .profile-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 0.875rem;
|
||||
border-radius: 10px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background: rgba(255,255,255,0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.elite-user .profile-trigger:hover {
|
||||
background: rgba(255,255,255,0.2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
.bg-gradient-primary {
|
||||
background: linear-gradient(135deg, var(--primary-400) 0%, var(--primary-600) 100%);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid white;
|
||||
}
|
||||
|
||||
.status-dot.online {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2);
|
||||
animation: pulse-dot 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.2); }
|
||||
50% { box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.4); }
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
font-size: 0.625rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: rgba(255,255,255,0.25);
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.session-timer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.icon-sm {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.timer-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 0.75rem;
|
||||
color: rgba(255,255,255,0.8);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.elite-user:hover .arrow {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
/* USER DROPDOWN MENU */
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.elite-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.5rem);
|
||||
right: 0;
|
||||
min-width: 340px;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 48px rgba(0,0,0,0.15);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transform: translateY(-10px) scale(0.95);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border: 1px solid var(--surface-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.elite-user:hover .elite-dropdown {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* Dropdown Header */
|
||||
.dropdown-header {
|
||||
padding: 1.25rem;
|
||||
background: linear-gradient(135deg, var(--primary-50) 0%, var(--surface-50) 100%);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.header-avatar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.avatar-lg {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-indicator.online {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
}
|
||||
|
||||
.status-indicator i {
|
||||
font-size: 6px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header-info .name {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.header-info .email {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.role-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 0.625rem;
|
||||
padding: 0.25rem 0.625rem;
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-600) 100%);
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
align-self: flex-start;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Session Card */
|
||||
.session-card {
|
||||
padding: 1rem 1.25rem;
|
||||
background: var(--surface-50);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.625rem;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-row .label i {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* Progress Bar */
|
||||
.progress-container {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 6px;
|
||||
background: var(--surface-200);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--green-400) 0%, var(--green-500) 100%);
|
||||
border-radius: 10px;
|
||||
transition: width 1s ease, background 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-fill[style*="width: 0%"],
|
||||
.progress-fill[style*="width: 1%"],
|
||||
.progress-fill[style*="width: 2%"],
|
||||
.progress-fill[style*="width: 3%"],
|
||||
.progress-fill[style*="width: 4%"],
|
||||
.progress-fill[style*="width: 5%"] {
|
||||
background: linear-gradient(90deg, var(--red-400) 0%, var(--red-500) 100%);
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: 0.625rem;
|
||||
color: var(--text-color-secondary);
|
||||
margin-top: 0.375rem;
|
||||
text-align: right;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Menu Sections */
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--surface-border);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.menu-section {
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
|
||||
.menu-section.compact {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-color-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 0.5rem 1.25rem 0.75rem;
|
||||
}
|
||||
|
||||
.section-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.875rem;
|
||||
padding: 0.75rem 1.25rem;
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background: var(--surface-100);
|
||||
}
|
||||
|
||||
.menu-item i:first-child {
|
||||
font-size: 1rem;
|
||||
color: var(--text-color-secondary);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.menu-item:hover i:first-child {
|
||||
color: var(--primary-color);
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
.menu-item span {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.arrow-right {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
margin-left: auto;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.menu-item:hover .arrow-right {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
.item-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
padding: 0 0.375rem;
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 700;
|
||||
border-radius: 10px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.value-badge {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
background: var(--surface-100);
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Logout Section */
|
||||
.logout-divider {
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
var(--red-200) 50%,
|
||||
transparent 100%);
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
.logout-section {
|
||||
padding: 0.75rem 0;
|
||||
background: linear-gradient(to bottom, white 0%, var(--red-50) 100%);
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.875rem;
|
||||
padding: 0.875rem 1.25rem;
|
||||
color: var(--red-600);
|
||||
font-weight: 600;
|
||||
font-size: 0.875rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.logout-btn:hover {
|
||||
background: var(--red-100);
|
||||
color: var(--red-700);
|
||||
}
|
||||
|
||||
.logout-btn i:first-child {
|
||||
font-size: 1rem;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.logout-btn:hover i:first-child {
|
||||
transform: scale(1.1) rotate(-10deg);
|
||||
}
|
||||
|
||||
.logout-btn .pi-lock {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
/* LOGOUT DIALOG */
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.elite-dialog .dialog-content {
|
||||
text-align: center;
|
||||
padding: 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--red-50) 0%, var(--red-100) 100%);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.icon-lg {
|
||||
font-size: 2.5rem;
|
||||
color: var(--red-500);
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: var(--surface-50);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
border: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.625rem;
|
||||
color: var(--text-color);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.info-item i {
|
||||
color: var(--primary-color);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-color-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.warning-text i {
|
||||
color: var(--blue-500);
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
justify-content: flex-end;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
/* UTILITY CLASSES */
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
|
||||
.text-green-600 { color: #059669 !important; }
|
||||
.text-yellow-600 { color: #d97706 !important; }
|
||||
.text-orange-600 { color: #ea580c !important; }
|
||||
.text-red-600 { color: #dc2626 !important; }
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
/* RESPONSIVE */
|
||||
/* ═══════════════════════════════════════════════════════════ */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.app-version { display: none; }
|
||||
.user-info { display: none; }
|
||||
.elite-dropdown { min-width: 300px; }
|
||||
.search-dropdown { min-width: 280px; }
|
||||
}
|
||||
98
src/main/resources/META-INF/resources/template.xhtml
Normal file
98
src/main/resources/META-INF/resources/template.xhtml
Normal file
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="jakarta.faces.html"
|
||||
xmlns:f="jakarta.faces.core"
|
||||
xmlns:ui="jakarta.faces.facelets"
|
||||
xmlns:p="http://primefaces.org/ui">
|
||||
|
||||
<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="apple-mobile-web-app-capable" content="yes"/>
|
||||
</f:facet>
|
||||
<title><ui:insert name="title">Lions User Manager</ui:insert></title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: var(--surface-b);
|
||||
color: var(--text-color);
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
.layout-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.layout-topbar {
|
||||
height: 4rem;
|
||||
padding: 0 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--surface-a);
|
||||
box-shadow: 0 2px 4px -1px rgba(0,0,0,.15);
|
||||
z-index: 1000;
|
||||
}
|
||||
.layout-content {
|
||||
padding: 2rem;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.layout-footer {
|
||||
padding: 1rem 2rem;
|
||||
background-color: var(--surface-a);
|
||||
text-align: center;
|
||||
}
|
||||
h1 { margin-top: 0; }
|
||||
.card {
|
||||
background: var(--surface-card);
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 2px 1px -1px rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 1px 3px 0 rgba(0,0,0,.12);
|
||||
}
|
||||
.mr-2 { margin-right: 0.5rem; }
|
||||
</style>
|
||||
<ui:insert name="head"/>
|
||||
</h:head>
|
||||
|
||||
<h:body>
|
||||
<div class="layout-wrapper">
|
||||
<div class="layout-topbar">
|
||||
<h:link outcome="/index" style="text-decoration: none; color: inherit; font-weight: bold; font-size: 1.5rem;">
|
||||
🦁 Lions User Manager
|
||||
</h:link>
|
||||
|
||||
<h:form>
|
||||
<p:menubar style="border:none; background:transparent;">
|
||||
<p:menuitem value="Dashboard" outcome="/index" icon="pi pi-home"/>
|
||||
<p:menuitem value="Utilisateurs" outcome="/pages/user-manager/users" icon="pi pi-users"/>
|
||||
<p:menuitem value="Rôles" outcome="/pages/user-manager/roles" icon="pi pi-lock"/>
|
||||
<p:menuitem value="Déconnexion" url="/auth/logout" icon="pi pi-power-off"/>
|
||||
</p:menubar>
|
||||
</h:form>
|
||||
</div>
|
||||
|
||||
<div class="layout-content">
|
||||
<ui:insert name="content"/>
|
||||
</div>
|
||||
|
||||
<div class="layout-footer">
|
||||
<span>Copyright 2024 - Lions User Manager Team</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p:growl id="growl" showDetail="true" life="3000">
|
||||
<p:autoUpdate/>
|
||||
</p:growl>
|
||||
|
||||
<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>
|
||||
</h:body>
|
||||
|
||||
</html>
|
||||
@@ -49,6 +49,11 @@
|
||||
<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>
|
||||
|
||||
@@ -1,70 +1,298 @@
|
||||
<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">
|
||||
|
||||
<!--
|
||||
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
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ Lions User Manager - Elite Topbar (Freya Design) ║
|
||||
║ Real-time Session Monitor | Modern UI | Professional ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
-->
|
||||
|
||||
<div class="layout-topbar">
|
||||
<h:outputStylesheet library="css" name="topbar-elite.css" />
|
||||
|
||||
<div class="layout-topbar lions-elite">
|
||||
<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" />
|
||||
<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" />
|
||||
</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">
|
||||
<li class="topbar-item user-profile">
|
||||
<a href="#" title="Profil utilisateur">
|
||||
<div class="flex align-items-center">
|
||||
<div class="bg-primary text-white border-round flex align-items-center justify-content-center mr-2"
|
||||
style="width: 32px; height: 32px; font-size: 12px; font-weight: bold;">
|
||||
<i class="pi pi-user"></i>
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<div class="text-900 font-medium">Utilisateur</div>
|
||||
<div class="text-600 text-xs">Connecté</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<li class="topbar-item">
|
||||
<a href="#" title="Actions rapides">
|
||||
<i class="topbar-icon pi pi-bolt" />
|
||||
</a>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/pages/user-manager/users/profile">
|
||||
<i class="pi pi-user mr-2"></i>
|
||||
<span>Mon Profil</span>
|
||||
</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>
|
||||
<a href="#">
|
||||
<i class="pi pi-cog mr-2"></i>
|
||||
<span>Paramètres</span>
|
||||
</a>
|
||||
<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="border-top-1 surface-border">
|
||||
<a href="#" class="text-red-600">
|
||||
<i class="pi pi-sign-out mr-2"></i>
|
||||
<span>Déconnexion</span>
|
||||
</a>
|
||||
<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" />
|
||||
</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" />
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<!-- 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>
|
||||
<div class="section-items">
|
||||
<h:link outcome="/pages/user-manager/users/profile" styleClass="menu-item">
|
||||
<i class="pi pi-user-edit" />
|
||||
<span>Mon Profil</span>
|
||||
<i class="pi pi-angle-right arrow-right" />
|
||||
</h:link>
|
||||
|
||||
<h:link outcome="/pages/user-manager/settings" styleClass="menu-item">
|
||||
<i class="pi pi-cog" />
|
||||
<span>Paramètres</span>
|
||||
<i class="pi pi-angle-right arrow-right" />
|
||||
</h:link>
|
||||
|
||||
<a href="#" class="menu-item">
|
||||
<i class="pi pi-shield" />
|
||||
<span>Sécurité</span>
|
||||
<i class="pi pi-angle-right arrow-right" />
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="divider" />
|
||||
|
||||
<!-- System -->
|
||||
<li class="menu-section">
|
||||
<div class="section-title">
|
||||
<i class="pi pi-cog" />
|
||||
Système
|
||||
</div>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ui:composition>
|
||||
<!-- LOGOUT DIALOG -->
|
||||
<p:dialog id="logoutDialog" widgetVar="logoutDialog" header="Confirmation de déconnexion" modal="true"
|
||||
closable="true" styleClass="elite-dialog" responsive="true" width="450">
|
||||
|
||||
<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>
|
||||
@@ -162,13 +162,13 @@
|
||||
<p:separator />
|
||||
<h3>Rechercher un rôle</h3>
|
||||
<div class="flex gap-2 mb-3">
|
||||
<p:inputText
|
||||
value="#{roleGestionBean.roleSearchText}"
|
||||
<p:inputText
|
||||
value="#{roleGestionBean.roleSearchText}"
|
||||
placeholder="Rechercher un rôle..."
|
||||
styleClass="flex-1">
|
||||
<p:ajax event="keyup"
|
||||
<p:ajax event="keyup"
|
||||
delay="300"
|
||||
update="roleSearchResults" />
|
||||
update="@parent" />
|
||||
</p:inputText>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
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: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)
|
||||
@@ -120,32 +121,35 @@
|
||||
icon="pi pi-trash"
|
||||
title="Supprimer"
|
||||
styleClass="p-button-text p-button-sm p-button-danger"
|
||||
onclick="PF('confirmDeleteRoleDialog').show()" />
|
||||
onclick="PF('confirmDeleteRoleDialog_#{fn:replace(fn:replace(role.id != null ? role.id : role.name, '-', '_'), ':', '_')}').show()" />
|
||||
</div>
|
||||
</c:if>
|
||||
</f:facet>
|
||||
</p:card>
|
||||
|
||||
<!-- Dialog de confirmation de suppression -->
|
||||
<p:confirmDialog
|
||||
id="confirmDeleteRoleDialog"
|
||||
widgetVar="confirmDeleteRoleDialog"
|
||||
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="#{roleBean.deleteRole(role.id)}"
|
||||
update="@form"
|
||||
oncomplete="PF('confirmDeleteRoleDialog').hide()" />
|
||||
<p:commandButton
|
||||
value="Non"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
onclick="PF('confirmDeleteRoleDialog').hide()" />
|
||||
</p:confirmDialog>
|
||||
<!-- 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>
|
||||
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
Paramètres:
|
||||
- value: String (requis) - Texte du bouton
|
||||
- icon: String (optionnel) - Classe d'icône PrimeIcons
|
||||
- action: String (optionnel) - Action à exécuter
|
||||
- outcome: String (optionnel) - Page de redirection
|
||||
- hasAction: Boolean (défaut: false) - Indique si une action 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"
|
||||
- disabled: Boolean (défaut: false) - Désactiver le bouton
|
||||
@@ -45,8 +48,8 @@
|
||||
<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="#{not empty action}" />
|
||||
<c:set var="hasOutcome" value="#{not empty outcome}" />
|
||||
<c:set var="hasAction" value="#{empty hasAction ? false : hasAction}" />
|
||||
<c:set var="hasOutcome" value="#{empty hasOutcome ? false : hasOutcome}" />
|
||||
|
||||
<!-- Déterminer la classe selon la severity -->
|
||||
<c:choose>
|
||||
@@ -85,14 +88,15 @@
|
||||
|
||||
<c:choose>
|
||||
<c:when test="#{hasAction}">
|
||||
<p:commandButton
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{not empty icon ? icon : ''}"
|
||||
styleClass="#{buttonClass}"
|
||||
disabled="#{disabled}"
|
||||
action="#{action}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="#{process}" />
|
||||
process="#{process}"
|
||||
onclick="#{not empty onclick ? onclick : ''}" />
|
||||
</c:when>
|
||||
<c:when test="#{hasOutcome}">
|
||||
<p:commandButton
|
||||
@@ -102,7 +106,17 @@
|
||||
disabled="#{disabled}"
|
||||
outcome="#{outcome}"
|
||||
update="#{not empty update ? update : '@form'}"
|
||||
process="#{process}" />
|
||||
process="#{process}"
|
||||
onclick="#{not empty onclick ? onclick : ''}" />
|
||||
</c:when>
|
||||
<c:when test="#{not empty onclick}">
|
||||
<p:commandButton
|
||||
value="#{value}"
|
||||
icon="#{not empty icon ? icon : ''}"
|
||||
styleClass="#{buttonClass}"
|
||||
disabled="#{disabled}"
|
||||
type="button"
|
||||
onclick="#{onclick}" />
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<p:commandButton
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
xmlns:fn="http://xmlns.jcp.org/jsp/jstl/functions">
|
||||
|
||||
<div class="p-4" style="min-height: 9rem;">
|
||||
<!-- Header: Titre et Icône -->
|
||||
@@ -16,7 +17,19 @@
|
||||
</div>
|
||||
|
||||
<!-- Valeur principale -->
|
||||
<div class="text-900 font-bold text-2xl mb-2">#{value}</div>
|
||||
<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: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:otherwise>#{value}</c:otherwise>
|
||||
</c:choose>
|
||||
</c:when>
|
||||
<c:otherwise>0</c:otherwise>
|
||||
</c:choose>
|
||||
</div>
|
||||
|
||||
<!-- Sous-titre -->
|
||||
<c:if test="#{not empty subtitle}">
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<ui:param name="icon" value="pi-users" />
|
||||
<ui:param name="iconColor" value="blue-600" />
|
||||
</ui:include>
|
||||
<!-- Autres KPI... -->
|
||||
Autres KPI à ajouter ici
|
||||
</ui:define>
|
||||
</ui:include>
|
||||
-->
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
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:c="http://xmlns.jcp.org/jsp/jstl/core"
|
||||
xmlns:lum="http://xmlns.jcp.org/jsf/composite/components">
|
||||
|
||||
<!--
|
||||
Composant réutilisable: Tableau Utilisateurs (WOU/DRY Pattern)
|
||||
@@ -25,6 +26,10 @@
|
||||
- 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
|
||||
- styleClass: String (optionnel) - Classes CSS supplémentaires
|
||||
|
||||
@@ -53,53 +58,80 @@
|
||||
<c:set var="showStatus" value="#{empty showStatus ? true : showStatus}" />
|
||||
<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
|
||||
<p:dataTable
|
||||
id="#{tableId}"
|
||||
value="#{users}"
|
||||
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}"
|
||||
styleClass="w-full #{styleClass}"
|
||||
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"
|
||||
emptyMessage="Aucun utilisateur trouvé">
|
||||
currentPageReportTemplate="Affichage {startRecord}-{endRecord} sur {totalRecords}"
|
||||
emptyMessage="Aucun utilisateur trouvé"
|
||||
reflow="true"
|
||||
responsiveLayout="scroll"
|
||||
lazy="#{not empty lazy and lazy}">
|
||||
|
||||
<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>
|
||||
</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: 3rem" />
|
||||
<p:column selectionMode="#{selectionMode}" style="width: 50px" />
|
||||
</c:if>
|
||||
|
||||
<!-- Colonne Username -->
|
||||
<p:column headerText="Nom d'utilisateur" sortBy="#{user.username}" style="width: 15%">
|
||||
<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-small" />
|
||||
<span class="font-semibold">#{user.username}</span>
|
||||
<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>
|
||||
</div>
|
||||
<span class="font-semibold text-900">#{user.username}</span>
|
||||
</div>
|
||||
</p:column>
|
||||
|
||||
<!-- Colonne Nom complet -->
|
||||
<p:column headerText="Nom complet" sortBy="#{user.nom}">
|
||||
<div class="flex flex-column">
|
||||
<span class="font-semibold">#{user.prenom} #{user.nom}</span>
|
||||
<p:column headerText="Nom complet" sortBy="#{user.nom}" style="width: 220px">
|
||||
<div class="flex flex-column py-2">
|
||||
<span class="font-medium text-900">#{user.prenom} #{user.nom}</span>
|
||||
<c:if test="#{not empty user.fonction}">
|
||||
<span class="text-color-secondary text-xs">#{user.fonction}</span>
|
||||
<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}">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<i class="pi pi-envelope text-color-secondary"></i>
|
||||
<span>#{user.email}</span>
|
||||
<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>
|
||||
<c:if test="#{user.emailVerified}">
|
||||
<i class="pi pi-check-circle text-green-500" title="Email vérifié"></i>
|
||||
<i class="pi pi-check-circle text-green-500 ml-2" title="Email vérifié"></i>
|
||||
</c:if>
|
||||
</div>
|
||||
</p:column>
|
||||
@@ -107,45 +139,50 @@
|
||||
|
||||
<!-- Colonne Statut -->
|
||||
<c:if test="#{showStatus}">
|
||||
<p:column headerText="Statut" sortBy="#{user.statut}">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<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'}" />
|
||||
<c:if test="#{user.enabled}">
|
||||
<i class="pi pi-check-circle text-green-500" title="Compte activé"></i>
|
||||
</c:if>
|
||||
<c:if test="#{not user.enabled}">
|
||||
<i class="pi pi-times-circle text-red-500" title="Compte désactivé"></i>
|
||||
</c:if>
|
||||
</div>
|
||||
</p:column>
|
||||
</c:if>
|
||||
|
||||
<!-- Colonne Rôles -->
|
||||
<c:if test="#{showRoles}">
|
||||
<p:column headerText="Rôles">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<c:forEach var="role" items="#{user.realmRoles}" varStatus="status">
|
||||
<c:if test="#{status.index < 3}">
|
||||
<p:tag value="#{role}" severity="info" styleClass="text-xs" />
|
||||
</c:if>
|
||||
</c:forEach>
|
||||
<c:if test="#{user.realmRoles != null and user.realmRoles.size() > 3}">
|
||||
<p:tag value="+#{user.realmRoles.size() - 3}" severity="secondary" styleClass="text-xs" />
|
||||
</c:if>
|
||||
<p:column headerText="Rôles" style="width: 200px">
|
||||
<div class="flex flex-wrap gap-1 py-2 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}">
|
||||
<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>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<span class="text-500 text-xs">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: 150px">
|
||||
<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:include>
|
||||
<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}" />
|
||||
</div>
|
||||
</p:column>
|
||||
</c:if>
|
||||
</p:dataTable>
|
||||
|
||||
@@ -78,9 +78,11 @@
|
||||
<c:when test="#{layout == 'dropdown'}">
|
||||
<p:commandButton
|
||||
icon="pi pi-ellipsis-v"
|
||||
styleClass="p-button-text p-button-sm"
|
||||
type="button">
|
||||
<p:menu>
|
||||
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:menu styleClass="w-12rem">
|
||||
<c:if test="#{showView}">
|
||||
<p:menuitem
|
||||
value="Voir le profil"
|
||||
|
||||
@@ -49,11 +49,11 @@
|
||||
<f:facet name="header">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<p:avatar
|
||||
label="#{user.firstName != null ? user.firstName.substring(0,1) : 'U'}#{user.lastName != null ? user.lastName.substring(0,1) : ''}"
|
||||
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.firstName} #{user.lastName}</h3>
|
||||
<h3 class="m-0">#{user.prenom} #{user.nom}</h3>
|
||||
<span class="text-color-secondary text-sm">@#{user.username}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
<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">
|
||||
|
||||
<!-- Informations de base -->
|
||||
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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" />
|
||||
|
||||
<!-- Ville -->
|
||||
<p:outputLabel for="ville" value="Ville" />
|
||||
<p:inputText id="ville"
|
||||
value="#{user.ville}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Abidjan"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- Pays -->
|
||||
<p:outputLabel for="pays" value="Pays" />
|
||||
<p:inputText id="pays"
|
||||
value="#{user.pays}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Côte d'Ivoire"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- 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}" />
|
||||
|
||||
<!-- 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>
|
||||
</c:if>
|
||||
|
||||
</p:panelGrid>
|
||||
|
||||
<!-- Champs mot de passe (si affichés) -->
|
||||
<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>
|
||||
</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" />
|
||||
</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" />
|
||||
</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" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if>
|
||||
<p:commandButton
|
||||
value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
outcome="#{not empty cancelOutcome ? cancelOutcome : '/pages/user-manager/users/list'}"
|
||||
immediate="true" />
|
||||
</div>
|
||||
</f:facet>
|
||||
</p:panel>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
- submitOutcome: String (optionnel) - Page de redirection après soumission
|
||||
- update: String (optionnel) - Composants à mettre à jour après soumission
|
||||
- hasSubmitAction: Boolean (optionnel) - Indicateur si submitAction est fourni (pour éviter l'évaluation)
|
||||
- useParentForm: Boolean (défaut: false) - Utiliser le formulaire parent au lieu de créer un nouveau formulaire
|
||||
|
||||
Exemples d'utilisation:
|
||||
|
||||
@@ -53,215 +54,42 @@
|
||||
<c:set var="showRealmSelector" value="#{empty showRealmSelector ? false : showRealmSelector}" />
|
||||
<c:set var="showPasswordFields" value="#{empty showPasswordFields ? true : showPasswordFields}" />
|
||||
<c:set var="readonly" value="#{empty readonly ? false : readonly}" />
|
||||
<c:set var="useParentForm" value="#{empty useParentForm ? false : useParentForm}" />
|
||||
|
||||
<h:form id="#{formId}">
|
||||
<p:panel header="#{mode == 'create' ? 'Nouvel Utilisateur' : 'Modifier Utilisateur'}"
|
||||
styleClass="w-full">
|
||||
|
||||
<!-- Informations de base -->
|
||||
<p:panelGrid columns="2" styleClass="w-full" columnClasses="col-12 md:col-6">
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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" />
|
||||
|
||||
<!-- Ville -->
|
||||
<p:outputLabel for="ville" value="Ville" />
|
||||
<p:inputText id="ville"
|
||||
value="#{user.ville}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Abidjan"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- Pays -->
|
||||
<p:outputLabel for="pays" value="Pays" />
|
||||
<p:inputText id="pays"
|
||||
value="#{user.pays}"
|
||||
readonly="#{readonly}"
|
||||
placeholder="Côte d'Ivoire"
|
||||
styleClass="w-full" />
|
||||
|
||||
<!-- 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}" />
|
||||
|
||||
<!-- 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>
|
||||
</c:if>
|
||||
|
||||
</p:panelGrid>
|
||||
|
||||
<!-- Champs mot de passe (si affichés) -->
|
||||
<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>
|
||||
</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" />
|
||||
</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" />
|
||||
</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" />
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
</c:if>
|
||||
<p:commandButton
|
||||
value="Annuler"
|
||||
icon="pi pi-times"
|
||||
styleClass="p-button-secondary"
|
||||
outcome="#{not empty cancelOutcome ? cancelOutcome : '/pages/user-manager/users/list'}"
|
||||
immediate="true" />
|
||||
</div>
|
||||
</f:facet>
|
||||
</p:panel>
|
||||
</h:form>
|
||||
<c:choose>
|
||||
<c:when test="#{useParentForm}">
|
||||
<!-- Utiliser le formulaire parent - pas de formulaire ici -->
|
||||
<ui:include src="/templates/components/user-management/user-form-content.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="mode" value="#{mode}" />
|
||||
<ui:param name="showRealmSelector" value="#{showRealmSelector}" />
|
||||
<ui:param name="showPasswordFields" value="#{showPasswordFields}" />
|
||||
<ui:param name="readonly" value="#{readonly}" />
|
||||
<ui:param name="submitAction" value="#{submitAction}" />
|
||||
<ui:param name="submitOutcome" value="#{submitOutcome}" />
|
||||
<ui:param name="update" value="#{update}" />
|
||||
<ui:param name="hasSubmitAction" value="#{hasSubmitAction}" />
|
||||
<ui:param name="cancelOutcome" value="#{cancelOutcome}" />
|
||||
</ui:include>
|
||||
</c:when>
|
||||
<c:otherwise>
|
||||
<!-- Créer son propre formulaire -->
|
||||
<h:form id="#{formId}">
|
||||
<ui:include src="/templates/components/user-management/user-form-content.xhtml">
|
||||
<ui:param name="user" value="#{user}" />
|
||||
<ui:param name="mode" value="#{mode}" />
|
||||
<ui:param name="showRealmSelector" value="#{showRealmSelector}" />
|
||||
<ui:param name="showPasswordFields" value="#{showPasswordFields}" />
|
||||
<ui:param name="readonly" value="#{readonly}" />
|
||||
<ui:param name="submitAction" value="#{submitAction}" />
|
||||
<ui:param name="submitOutcome" value="#{submitOutcome}" />
|
||||
<ui:param name="update" value="#{update}" />
|
||||
<ui:param name="hasSubmitAction" value="#{hasSubmitAction}" />
|
||||
<ui:param name="cancelOutcome" value="#{cancelOutcome}" />
|
||||
</ui:include>
|
||||
</h:form>
|
||||
</c:otherwise>
|
||||
</c:choose>
|
||||
|
||||
</ui:composition>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:fr="http://primefaces.org/freya"
|
||||
lang="fr">
|
||||
|
||||
<h:head>
|
||||
@@ -47,6 +48,7 @@
|
||||
<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,53 +1,69 @@
|
||||
# ============================================================================
|
||||
# Configuration Développement - Lions User Manager Client
|
||||
# Lions User Manager Client - Configuration Développement
|
||||
# ============================================================================
|
||||
# NOTE: La configuration OIDC principale est dans application.properties
|
||||
# avec le préfixe %dev. Ce fichier contient UNIQUEMENT les surcharges
|
||||
# spécifiques au développement qui ne sont pas déjà dans application.properties
|
||||
# Ce fichier contient TOUTES les propriétés spécifiques au développement
|
||||
# Il surcharge et complète application.properties
|
||||
# ============================================================================
|
||||
|
||||
# ============================================
|
||||
# Logging - Surcharges DEV
|
||||
# HTTP Configuration DEV
|
||||
# ============================================
|
||||
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)
|
||||
# ============================================
|
||||
# Logging plus détaillé en dev
|
||||
quarkus.log.console.level=DEBUG
|
||||
quarkus.log.category."dev.lions.user.manager".level=TRACE
|
||||
# Debug OIDC pour voir quelle valeur est chargée
|
||||
quarkus.log.category."io.quarkus.oidc".level=DEBUG
|
||||
quarkus.log.category."io.quarkus.oidc.runtime".level=TRACE
|
||||
quarkus.log.category."io.quarkus.oidc.runtime".level=DEBUG
|
||||
|
||||
# ============================================
|
||||
# MyFaces - Surcharges DEV
|
||||
# MyFaces DEV
|
||||
# ============================================
|
||||
quarkus.myfaces.project-stage=Development
|
||||
quarkus.myfaces.check-id-production-mode=false
|
||||
|
||||
# ============================================
|
||||
# Backend - Surcharges DEV
|
||||
# Backend REST Client DEV
|
||||
# ============================================
|
||||
# Backend local (le serveur tourne sur le port 8081)
|
||||
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
|
||||
|
||||
# ============================================
|
||||
# CORS - Surcharges DEV
|
||||
# CORS DEV (permissif)
|
||||
# ============================================
|
||||
# CORS permissif en dev (surcharge de application.properties)
|
||||
quarkus.http.cors.origins=*
|
||||
quarkus.http.cors.origins=http://localhost:8080,http://localhost:8081,http://localhost:8082
|
||||
|
||||
# ============================================
|
||||
# OIDC - Surcharges DEV (si nécessaire)
|
||||
# OIDC Configuration DEV - Keycloak Local
|
||||
# ============================================
|
||||
# NOTE: La configuration OIDC principale est dans application.properties
|
||||
# avec le préfixe %dev. (lignes 73-81)
|
||||
# Ne définir ici QUE les propriétés qui ne sont pas déjà dans application.properties
|
||||
#
|
||||
# State Secret pour PKCE (OBLIGATOIRE quand pkce-required=true)
|
||||
# Ce secret est utilisé pour encrypter le PKCE code verifier dans le state cookie
|
||||
# Minimum 16 caractères requis, recommandé 32 caractères
|
||||
# IMPORTANT: Ne PAS définir pkce-secret quand state-secret est défini (conflit Quarkus)
|
||||
# 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
|
||||
# Surcharge de encryption-secret (64 caractères pour garantir)
|
||||
# Cette propriété est aussi définie dans application.properties avec %dev.,
|
||||
# mais on la redéfinit ici pour garantir qu'elle soit chargée
|
||||
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/*
|
||||
|
||||
@@ -1,18 +1,61 @@
|
||||
# ============================================================================
|
||||
# Configuration Production - Lions User Manager Client
|
||||
# Lions User Manager Client - Configuration Production
|
||||
# ============================================================================
|
||||
# NOTE: La configuration OIDC principale est dans application.properties
|
||||
# avec le préfixe %prod. (lignes 86-94)
|
||||
#
|
||||
# Ce fichier peut être utilisé pour des surcharges spécifiques à la production
|
||||
# qui ne sont pas déjà définies dans application.properties avec %prod.
|
||||
#
|
||||
# Exemple d'utilisation :
|
||||
# - Surcharges de logging spécifiques à la production
|
||||
# - Configurations spécifiques à un environnement de production particulier
|
||||
# - Variables d'environnement qui doivent être surchargées
|
||||
# Ce fichier contient TOUTES les propriétés spécifiques à la production
|
||||
# Il surcharge et complète application.properties
|
||||
# ============================================================================
|
||||
|
||||
# Exemple (décommenter si nécessaire) :
|
||||
# quarkus.log.console.level=WARN
|
||||
# quarkus.log.category."dev.lions.user.manager".level=INFO
|
||||
# ============================================
|
||||
# HTTP Configuration PROD
|
||||
# ============================================
|
||||
quarkus.http.port=8080
|
||||
|
||||
# Session cookies sécurisés en prod (HTTPS uniquement)
|
||||
quarkus.http.session-cookie-secure=true
|
||||
|
||||
# ============================================
|
||||
# Logging PROD (moins verbeux)
|
||||
# ============================================
|
||||
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
|
||||
|
||||
# ============================================
|
||||
# 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}
|
||||
|
||||
# ============================================
|
||||
# CORS PROD (restrictif)
|
||||
# ============================================
|
||||
# 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}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
# Configuration Lions User Manager Client
|
||||
# ============================================================================
|
||||
# Lions User Manager Client - Configuration Commune
|
||||
# ============================================================================
|
||||
# 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)
|
||||
# ============================================================================
|
||||
|
||||
# ============================================
|
||||
# Application Info
|
||||
# ============================================
|
||||
quarkus.application.name=lions-user-manager-client
|
||||
quarkus.application.version=1.0.0
|
||||
|
||||
# Configuration HTTP
|
||||
quarkus.http.port=8080
|
||||
# ============================================
|
||||
# Configuration HTTP (commune)
|
||||
# ============================================
|
||||
quarkus.http.host=0.0.0.0
|
||||
quarkus.http.root-path=/
|
||||
quarkus.http.so-reuse-port=true
|
||||
@@ -12,29 +24,34 @@ quarkus.http.so-reuse-port=true
|
||||
quarkus.http.session-timeout=60m
|
||||
quarkus.http.session-cookie-same-site=lax
|
||||
quarkus.http.session-cookie-http-only=true
|
||||
quarkus.http.session-cookie-secure=false
|
||||
|
||||
# Configuration logging
|
||||
# ============================================
|
||||
# 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
|
||||
quarkus.myfaces.project-stage=Development
|
||||
# ============================================
|
||||
# MyFaces Configuration (commune)
|
||||
# ============================================
|
||||
quarkus.myfaces.state-saving-method=server
|
||||
quarkus.myfaces.number-of-views-in-session=50
|
||||
quarkus.myfaces.number-of-sequential-views-in-session=10
|
||||
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=3600000
|
||||
quarkus.myfaces.view-expired-exception-handler-redirect-page=/
|
||||
quarkus.myfaces.check-id-production-mode=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
|
||||
@@ -44,85 +61,57 @@ primefaces.UPLOADER=commons
|
||||
primefaces.AUTO_UPDATE=false
|
||||
primefaces.CACHE_PROVIDER=org.primefaces.cache.DefaultCacheProvider
|
||||
|
||||
# Configuration Backend Lions User Manager
|
||||
lions.user.manager.backend.url=${LIONS_USER_MANAGER_BACKEND_URL:http://localhost:8081}
|
||||
|
||||
# Configuration REST Client
|
||||
quarkus.rest-client."lions-user-manager-api".url=${lions.user.manager.backend.url}
|
||||
# ============================================
|
||||
# Configuration REST Client (commune)
|
||||
# ============================================
|
||||
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
|
||||
|
||||
# ============================================
|
||||
# OIDC Configuration - Base (All Environments)
|
||||
# Configuration OIDC - Base (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.cookie-same-site=lax
|
||||
quarkus.oidc.authentication.java-script-auto-redirect=false
|
||||
quarkus.oidc.discovery-enabled=true
|
||||
quarkus.oidc.verify-access-token=true
|
||||
quarkus.security.auth.enabled=true
|
||||
|
||||
# ============================================
|
||||
# OIDC Configuration - DEV Profile
|
||||
# ============================================
|
||||
%dev.quarkus.oidc.auth-server-url=http://localhost:8180/realms/lions-user-manager
|
||||
%dev.quarkus.oidc.client-id=lions-user-manager-client
|
||||
%dev.quarkus.oidc.credentials.secret=NTuaQpk5E6qiMqAWTFrCOcIkOABzZzKO
|
||||
%dev.quarkus.oidc.token.issuer=http://localhost:8180/realms/lions-user-manager
|
||||
%dev.quarkus.oidc.tls.verification=none
|
||||
%dev.quarkus.oidc.authentication.pkce-required=true
|
||||
# State Secret pour PKCE (OBLIGATOIRE quand pkce-required=true)
|
||||
# Ce secret est utilisé pour encrypter le PKCE code verifier dans le state cookie
|
||||
# Minimum 16 caractères requis, recommandé 32 caractères
|
||||
# IMPORTANT: Ne PAS définir pkce-secret quand state-secret est défini (conflit Quarkus)
|
||||
%dev.quarkus.oidc.authentication.state-secret=NTuaQpk5E6qiMqAWTFrCOcIkOABzZzKO
|
||||
# Secret de chiffrement pour le token state manager (minimum 16 caractères requis)
|
||||
# Cette clé est utilisée pour chiffrer les cookies d'état OIDC
|
||||
# Valeur: 64 caractères (secret Keycloak dupliqué pour garantir la longueur)
|
||||
%dev.quarkus.oidc.token-state-manager.encryption-secret=NTuaQpk5E6qiMqAWTFrCOcIkOABzZzKO
|
||||
# Extraction des rôles depuis le token
|
||||
quarkus.oidc.roles.role-claim-path=realm_access/roles
|
||||
quarkus.oidc.roles.source=accesstoken
|
||||
|
||||
# ============================================
|
||||
# OIDC Configuration - PROD Profile
|
||||
# ============================================
|
||||
%prod.quarkus.oidc.auth-server-url=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/master}
|
||||
%prod.quarkus.oidc.client-id=${KEYCLOAK_CLIENT_ID:lions-user-manager-client}
|
||||
%prod.quarkus.oidc.credentials.secret=${KEYCLOAK_CLIENT_SECRET}
|
||||
%prod.quarkus.oidc.token.issuer=${KEYCLOAK_AUTH_SERVER_URL:https://security.lions.dev/realms/master}
|
||||
%prod.quarkus.oidc.tls.verification=required
|
||||
%prod.quarkus.oidc.authentication.cookie-same-site=strict
|
||||
%prod.quarkus.oidc.authentication.pkce-required=false
|
||||
# Secret production via variable d'environnement (32 caractères requis)
|
||||
%prod.quarkus.oidc.token-state-manager.encryption-secret=${OIDC_ENCRYPTION_SECRET}
|
||||
|
||||
# Chemins publics (non protégés par OIDC)
|
||||
# Note: Les pages JSF sont servies via /pages/*.xhtml
|
||||
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.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
|
||||
|
||||
# Chemins protégés (requièrent authentification) - Désactivé en dev
|
||||
# Chemins protégés (requièrent authentification)
|
||||
quarkus.http.auth.permission.authenticated.paths=/pages/user-manager/*
|
||||
quarkus.http.auth.permission.authenticated.policy=authenticated
|
||||
|
||||
# DEV: Chemins publics élargis pour faciliter le développement
|
||||
%dev.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/*
|
||||
|
||||
# CORS (si nécessaire pour développement)
|
||||
# ============================================
|
||||
# CORS (configuration de base)
|
||||
# ============================================
|
||||
quarkus.http.cors=true
|
||||
quarkus.http.cors.origins=${CORS_ORIGINS:http://localhost:8080,http://localhost:8081}
|
||||
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 (optionnel)
|
||||
# ============================================
|
||||
# Metrics
|
||||
# ============================================
|
||||
quarkus.micrometer.export.prometheus.enabled=true
|
||||
quarkus.micrometer.export.prometheus.path=/metrics
|
||||
|
||||
|
||||
Reference in New Issue
Block a user