From c3973b6cfb2b765541c3f71278aab17db2a46b9b Mon Sep 17 00:00:00 2001 From: anomny Date: Mon, 3 Nov 2025 20:06:06 +0100 Subject: [PATCH 1/8] Clear picture in between loads --- src/Models/GroupModel.cs | 1 + src/Services/MigrationService.cs | 5 ++++- src/Views/Home/Users.cshtml | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Models/GroupModel.cs b/src/Models/GroupModel.cs index 389349a..f327718 100644 --- a/src/Models/GroupModel.cs +++ b/src/Models/GroupModel.cs @@ -21,6 +21,7 @@ public class GroupModel string? descriptionValue = ldapData.GetValueOrDefault("description"); if (descriptionValue is null) { + DisplayName = Cn; Permissions = []; } else diff --git a/src/Services/MigrationService.cs b/src/Services/MigrationService.cs index ecef3fc..d68f242 100644 --- a/src/Services/MigrationService.cs +++ b/src/Services/MigrationService.cs @@ -23,7 +23,10 @@ public class MigrationService : IHostedService await MigrateAsync(); } - public async Task StopAsync(CancellationToken cancellationToken) { } + public async Task StopAsync(CancellationToken cancellationToken) + { + await Task.CompletedTask; + } public async Task MigrateAsync() { diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index c74f6c7..3e5a255 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -758,6 +758,7 @@ // Photo const imgEl = row.querySelector('td:first-child img'); const detailPhoto = document.getElementById('detailPhoto'); + detailPhoto.src = ''; detailPhoto.src = `/Home/UserPhoto?uid=${data.uid}&size=256`; detailModal.show(); }); From edac9f28d8d5cbf6c36aaaa74e99d654a8488100 Mon Sep 17 00:00:00 2001 From: anomny Date: Mon, 3 Nov 2025 20:23:05 +0100 Subject: [PATCH 2/8] Add pagination --- src/Controllers/HomeController.cs | 44 +++++++++++++++++--- src/Models/UserModels.cs | 1 + src/Models/UsersViewModel.cs | 4 ++ src/Views/Home/Users.cshtml | 69 ++++++++++++++++++++++++++++++- 4 files changed, 112 insertions(+), 6 deletions(-) diff --git a/src/Controllers/HomeController.cs b/src/Controllers/HomeController.cs index 4b30b38..4578bfa 100644 --- a/src/Controllers/HomeController.cs +++ b/src/Controllers/HomeController.cs @@ -75,22 +75,56 @@ public class HomeController : Controller [Authorize(Roles = "CanManageUsers")] [HttpGet("Users")] - public async Task UsersAsync() + public async Task UsersAsync([FromQuery] int page = 1, [FromQuery] int pageSize = 50) { - IEnumerable users = await _ldap.ListUsersAsync([.. _ldap.UsersAttributes.Where(attr => attr != "jpegPhoto" && attr != "userPassword")]); + page = Math.Max(1, page); + pageSize = Math.Clamp(pageSize, 10, 100); + + // Fetch all users with jpegPhoto (but not userPassword) + IEnumerable allUsers = await _ldap.ListUsersAsync([.. _ldap.UsersAttributes.Where(attr => attr != "userPassword")]); + List usersList = allUsers.ToList(); + int totalUsers = usersList.Count; + + List paginatedUsers = usersList + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToList(); + List UserTableViewModels = []; - foreach (UserModel user in users) + foreach (UserModel user in paginatedUsers) { + string? jpegPhotoBase64 = null; + if (user.JpegPhoto != null) + { + try + { + byte[] photoBytes = Convert.FromBase64String(user.JpegPhoto); + jpegPhotoBase64 = ImageHelper.ResizeAndConvertToBase64(photoBytes, 48); + } + catch + { + // If image processing fails, leave as null + } + } + UserTableViewModels.Add(new() { Name = user.Cn ?? "", Surname = user.Sn ?? "", Title = user.Title ?? "", Uid = user.Uid, - Description = user.Description ?? new() {Address = new(), BirthDate = "", Workplace = "", Groups = []} + Description = user.Description ?? new() {Address = new(), BirthDate = "", Workplace = "", Groups = []}, + JpegPhotoBase64 = jpegPhotoBase64 }); } - return View(new UsersIndexViewModel() { UserTableViewModels = UserTableViewModels }); + + return View(new UsersIndexViewModel() + { + UserTableViewModels = UserTableViewModels, + CurrentPage = page, + PageSize = pageSize, + TotalUsers = totalUsers + }); } [Authorize(Roles = "CanManageUsers")] diff --git a/src/Models/UserModels.cs b/src/Models/UserModels.cs index 9fa36d0..5821e62 100644 --- a/src/Models/UserModels.cs +++ b/src/Models/UserModels.cs @@ -69,4 +69,5 @@ public class UserTableViewModel public required string Name { get; set; } public required string Surname { get; set; } public required UserDescription Description { get; set; } + public string? JpegPhotoBase64 { get; set; } } \ No newline at end of file diff --git a/src/Models/UsersViewModel.cs b/src/Models/UsersViewModel.cs index 1729968..3e18d30 100644 --- a/src/Models/UsersViewModel.cs +++ b/src/Models/UsersViewModel.cs @@ -3,4 +3,8 @@ namespace Berufsschule_HAM.Models; public class UsersIndexViewModel { public required IEnumerable UserTableViewModels { get; set; } + public int CurrentPage { get; set; } = 1; + public int PageSize { get; set; } = 50; + public int TotalUsers { get; set; } + public int TotalPages => (int)Math.Ceiling((double)TotalUsers / PageSize); } \ No newline at end of file diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 3e5a255..3e3ec25 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -41,7 +41,16 @@ { - Photo + @if (!string.IsNullOrEmpty(userTableViewModel.JpegPhotoBase64)) + { + Photo + } + else + { +
+ @userTableViewModel.Name.FirstOrDefault()@userTableViewModel.Surname.FirstOrDefault() +
+ } @userTableViewModel.Uid @userTableViewModel.Title @@ -79,6 +88,64 @@ + + @* Pagination Controls *@ + @if (Model.TotalPages > 1) + { + + } From 981d3614b984da47fa9b50f7cdf6655086041312 Mon Sep 17 00:00:00 2001 From: anomny Date: Mon, 3 Nov 2025 20:37:37 +0100 Subject: [PATCH 3/8] Show the total user count --- src/Views/Home/Users.cshtml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 3e3ec25..b41b72f 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -14,11 +14,18 @@

@T["Users"]

- -
+ +
+
+ @Model.TotalUsers @T["users"] @T["total"] + @if (Model.TotalPages > 1) + { + | @T["Page"] @Model.CurrentPage @T["of"] @Model.TotalPages + } +
From cd0173a38c588c076eb02cd568c060b0a94c6ac6 Mon Sep 17 00:00:00 2001 From: anomny Date: Wed, 5 Nov 2025 19:45:31 +0100 Subject: [PATCH 4/8] Add server-side filtering with AJAX pagination for users --- src/Controllers/HomeController.cs | 32 ++- src/Views/Home/Users.cshtml | 310 ++++++++++++++++++++++++------ 2 files changed, 282 insertions(+), 60 deletions(-) diff --git a/src/Controllers/HomeController.cs b/src/Controllers/HomeController.cs index 4578bfa..4cab327 100644 --- a/src/Controllers/HomeController.cs +++ b/src/Controllers/HomeController.cs @@ -75,7 +75,14 @@ public class HomeController : Controller [Authorize(Roles = "CanManageUsers")] [HttpGet("Users")] - public async Task UsersAsync([FromQuery] int page = 1, [FromQuery] int pageSize = 50) + public async Task UsersAsync( + [FromQuery] int page = 1, + [FromQuery] int pageSize = 50, + [FromQuery] string? username = null, + [FromQuery] string? title = null, + [FromQuery] string? name = null, + [FromQuery] string? surname = null, + [FromQuery] string? workplace = null) { page = Math.Max(1, page); pageSize = Math.Clamp(pageSize, 10, 100); @@ -83,6 +90,29 @@ public class HomeController : Controller // Fetch all users with jpegPhoto (but not userPassword) IEnumerable allUsers = await _ldap.ListUsersAsync([.. _ldap.UsersAttributes.Where(attr => attr != "userPassword")]); List usersList = allUsers.ToList(); + + // Apply filters + if (!string.IsNullOrWhiteSpace(username)) + { + usersList = usersList.Where(u => u.Uid?.Contains(username, StringComparison.OrdinalIgnoreCase) == true).ToList(); + } + if (!string.IsNullOrWhiteSpace(title)) + { + usersList = usersList.Where(u => u.Title?.Contains(title, StringComparison.OrdinalIgnoreCase) == true).ToList(); + } + if (!string.IsNullOrWhiteSpace(name)) + { + usersList = usersList.Where(u => u.Cn?.Contains(name, StringComparison.OrdinalIgnoreCase) == true).ToList(); + } + if (!string.IsNullOrWhiteSpace(surname)) + { + usersList = usersList.Where(u => u.Sn?.Contains(surname, StringComparison.OrdinalIgnoreCase) == true).ToList(); + } + if (!string.IsNullOrWhiteSpace(workplace)) + { + usersList = usersList.Where(u => u.Description?.Workplace?.Contains(workplace, StringComparison.OrdinalIgnoreCase) == true).ToList(); + } + int totalUsers = usersList.Count; List paginatedUsers = usersList diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index b41b72f..e888447 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -19,7 +19,7 @@ -
+
@Model.TotalUsers @T["users"] @T["total"] @if (Model.TotalPages > 1) { @@ -28,8 +28,40 @@
- -
+ @* Filter Section *@ +
+
+
@T["Filter Users"]
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + + +
@@ -42,7 +74,7 @@ - + @{ foreach (UserTableViewModel userTableViewModel in Model.UserTableViewModels) { @@ -97,64 +129,224 @@ @* Pagination Controls *@ - @if (Model.TotalPages > 1) - { - + +
@T["Action"]
@@ -74,7 +42,7 @@ - + @{ foreach (UserTableViewModel userTableViewModel in Model.UserTableViewModels) { @@ -129,224 +97,64 @@ @* Pagination Controls *@ - + + } - - @@ -88,64 +79,6 @@
@T["Action"]
- @if (!string.IsNullOrEmpty(userTableViewModel.JpegPhotoBase64)) - { - Photo - } - else - { -
- @userTableViewModel.Name.FirstOrDefault()@userTableViewModel.Surname.FirstOrDefault() -
- } + Photo
@userTableViewModel.Uid @userTableViewModel.Title
- - @* Pagination Controls *@ - @if (Model.TotalPages > 1) - { - - }
From 16e82ab16775b229409936a600848056f2f54b52 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Thu, 6 Nov 2025 09:26:05 +0100 Subject: [PATCH 8/8] Added small pre-cached image pre-loading, fixed layout shift for user preview --- src/Views/Home/Users.cshtml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 3e5a255..fd18b15 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -659,7 +659,7 @@