8 Commits

Author SHA1 Message Date
ucef 19cee92437 feat: add active user session tracking and visualization in admin dashboard
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 4m1s
AS Talange CI/CD Pipeline / Deploy to Test Environment (push) Successful in 5m34s
2026-07-03 17:18:14 +02:00
ucef 214ec08229 fix: resolve HTMX pagination bug on adherents list 2026-07-03 17:07:42 +02:00
ucef 62209ae659 fix(db): increase taille and numero length in dotation to 50
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m9s
AS Talange CI/CD Pipeline / Deploy to Test Environment (push) Successful in 4m29s
2026-07-01 15:00:50 +02:00
ucef fc3373a1b4 feat(ui): ajout d'un tooltip sur les noms d'équipements tronqués dans la vue dotation
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m13s
AS Talange CI/CD Pipeline / Deploy to Test Environment (push) Successful in 4m39s
2026-07-01 10:35:00 +02:00
ucef db3eb3ab9a feat: add sexe to pre-inscription form and entity 2026-07-01 09:32:42 +02:00
ucef d1f38f11f0 feat: add search filters to categories and equipements lists
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m16s
AS Talange CI/CD Pipeline / Deploy to Test Environment (push) Successful in 4m23s
2026-06-30 15:11:16 +02:00
ucef acf9d6d301 fix: apply maillot price and default selection for all new registrations
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m0s
AS Talange CI/CD Pipeline / Deploy to Test Environment (push) Successful in 4m19s
2026-06-30 14:42:59 +02:00
ucef 3066ff0e86 chore: bump version to 1.5-SNAPSHOT
AS Talange CI/CD Pipeline / Build & Run Unit Tests (push) Successful in 3m33s
AS Talange CI/CD Pipeline / Deploy to Test Environment (push) Successful in 4m23s
2026-06-30 13:20:04 +02:00
11 changed files with 131 additions and 9 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>as-talange-parent</artifactId>
<groupId>com.astalange</groupId>
<version>1.4</version>
<version>1.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>as-talange-parent</artifactId>
<groupId>com.astalange</groupId>
<version>1.4</version>
<version>1.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -18,7 +18,7 @@ public class Dotation {
@JoinColumn(name = "equipement_id", nullable = false)
private Equipement equipement;
@Column(length = 10)
@Column(length = 50)
private String taille;
@Column(length = 50)
@@ -30,7 +30,7 @@ public class Dotation {
@Column(nullable = false)
private Boolean fourni = false;
@Column(length = 10)
@Column(length = 50)
private String numero;
@Column(nullable = false)
@@ -0,0 +1,2 @@
ALTER TABLE dotation ALTER COLUMN taille TYPE VARCHAR(50);
ALTER TABLE dotation ALTER COLUMN numero TYPE VARCHAR(50);
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>as-talange-parent</artifactId>
<groupId>com.astalange</groupId>
<version>1.4</version>
<version>1.5-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -10,6 +10,9 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.session.HttpSessionEventPublisher;
@Configuration
@EnableWebSecurity
@@ -41,6 +44,10 @@ public class SecurityConfig {
.logout(logout -> logout
.logoutSuccessUrl("/login?logout")
.permitAll()
)
.sessionManagement(session -> session
.maximumSessions(100)
.sessionRegistry(sessionRegistry())
);
return http.build();
}
@@ -57,4 +64,14 @@ public class SecurityConfig {
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}
@@ -0,0 +1,37 @@
package com.astalange.web.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
import java.util.stream.Collectors;
@Controller
@RequestMapping("/admin/sessions")
public class SessionController {
private final SessionRegistry sessionRegistry;
public SessionController(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
@GetMapping
@PreAuthorize("hasRole('ADMIN')")
public String viewSessions(Model model) {
List<Object> principals = sessionRegistry.getAllPrincipals();
List<String> activeUsers = principals.stream()
.filter(principal -> principal instanceof UserDetails)
.map(principal -> ((UserDetails) principal).getUsername())
.collect(Collectors.toList());
model.addAttribute("activeUsers", activeUsers);
model.addAttribute("activeCount", activeUsers.size());
return "admin/sessions";
}
}
@@ -175,7 +175,7 @@
hx-target="#adherents-table-container"
hx-select="#adherents-table-container"
hx-include="#filter-row input, #filter-row select, input[name='query']"
th:attr="hx-vals=|{'page': ${currentPage - 1}}|"
th:attr="hx-vals=|{&quot;page&quot;: ${currentPage - 1}}|"
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors">
Précédent
</button>
@@ -190,7 +190,7 @@
hx-target="#adherents-table-container"
hx-select="#adherents-table-container"
hx-include="#filter-row input, #filter-row select, input[name='query']"
th:attr="hx-vals=|{'page': ${pageNum}}|"
th:attr="hx-vals=|{&quot;page&quot;: ${pageNum}}|"
th:text="${pageNum + 1}"
class="px-3 py-1 rounded transition-colors"
th:classappend="${currentPage == pageNum ? 'bg-blue-600 text-white' : 'border border-gray-300 hover:bg-gray-100'}">
@@ -205,7 +205,7 @@
hx-target="#adherents-table-container"
hx-select="#adherents-table-container"
hx-include="#filter-row input, #filter-row select, input[name='query']"
th:attr="hx-vals=|{'page': ${currentPage + 1}}|"
th:attr="hx-vals=|{&quot;page&quot;: ${currentPage + 1}}|"
class="px-3 py-1 border border-gray-300 rounded hover:bg-gray-100 transition-colors">
Suivant
</button>
@@ -0,0 +1,65 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Utilisateurs Connectés - AS Talange</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@1.9.11"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; }
</style>
</head>
<body class="bg-gray-50 text-gray-900 flex h-screen overflow-hidden">
<!-- Sidebar -->
<div th:replace="~{fragments/sidebar :: sidebar}"></div>
<!-- Main Content -->
<main class="flex-1 flex flex-col h-screen overflow-hidden">
<!-- Header -->
<header class="h-16 bg-white border-b border-gray-200 flex items-center justify-between px-6">
<h2 class="text-lg font-semibold text-gray-800">Utilisateurs Connectés</h2>
</header>
<!-- Main section -->
<div class="flex-1 overflow-auto p-6">
<div class="mb-6 flex justify-between items-center">
<h3 class="text-xl font-bold text-gray-900">
Sessions Actives (<span th:text="${activeCount}">0</span>)
</h3>
<button hx-get="/admin/sessions" hx-target="body" hx-swap="outerHTML" class="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors">
Rafraîchir
</button>
</div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<table class="w-full text-left border-collapse">
<thead>
<tr class="bg-gray-50 text-gray-500 text-sm uppercase tracking-wider border-b border-gray-200">
<th class="py-3 px-6 font-medium text-left">Nom d'utilisateur</th>
<th class="py-3 px-6 font-medium text-left">Statut</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 text-sm">
<tr th:if="${#lists.isEmpty(activeUsers)}">
<td colspan="2" class="py-8 text-center text-gray-500">Aucun utilisateur connecté pour le moment.</td>
</tr>
<tr th:each="username : ${activeUsers}" class="hover:bg-gray-50 transition-colors">
<td class="py-4 px-6 font-medium text-gray-900 flex items-center">
<div class="w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center font-bold mr-3">
<span th:text="${#strings.substring(username, 0, 1).toUpperCase()}">U</span>
</div>
<span th:text="${username}">username</span>
</td>
<td class="py-4 px-6">
<span class="px-2 py-1 bg-green-100 text-green-800 text-xs font-semibold rounded-full">En ligne</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</main>
</body>
</html>
@@ -44,6 +44,7 @@
<div class="pt-4 pb-2 px-3 text-xs font-semibold text-gray-400 uppercase tracking-wider">Administration</div>
<a href="/utilisateurs" th:classappend="${requestURI != null and requestURI.startsWith('/utilisateurs') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Utilisateurs</a>
<a href="/admin/logs/paiements" th:classappend="${requestURI != null and requestURI.startsWith('/admin/logs/paiements') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Logs Paiements</a>
<a href="/admin/sessions" th:classappend="${requestURI != null and requestURI.startsWith('/admin/sessions') ? 'bg-blue-50 text-blue-700 font-medium' : 'text-gray-600 hover:bg-gray-50 hover:text-gray-900'}" class="block px-3 py-2 rounded-lg transition-colors">Utilisateurs Connectés</a>
</th:block>
</nav>
<div class="p-4 border-t border-gray-200">
+1 -1
View File
@@ -13,7 +13,7 @@
<groupId>com.astalange</groupId>
<artifactId>as-talange-parent</artifactId>
<version>1.4</version>
<version>1.5-SNAPSHOT</version>
<packaging>pom</packaging>
<name>as-talange-parent</name>