Merge pull request #184 from LD-Reborn/183-bug-lighthouse-scores-way-down-due-to-user-images-being-uncompressed

183 bug lighthouse scores way down due to user images being uncompressed
This commit is contained in:
LD50
2025-10-26 16:26:48 +01:00
committed by GitHub
5 changed files with 69 additions and 9 deletions

View File

@@ -9,6 +9,7 @@
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="4.0.0" /> <PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="4.0.0" />
<PackageReference Include="Serilog" Version="4.3.0" /> <PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.4" /> <PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="9.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.4" /> <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.4" />

View File

@@ -7,6 +7,7 @@ using Berufsschule_HAM.Services;
using ElmahCore; using ElmahCore;
using Berufsschule_HAM.Exceptions; using Berufsschule_HAM.Exceptions;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Berufsschule_HAM.Helpers;
[ApiExplorerSettings(IgnoreApi = true)] [ApiExplorerSettings(IgnoreApi = true)]
[Route("[controller]")] [Route("[controller]")]
@@ -82,9 +83,10 @@ public class HomeController : Controller
List<UserTableViewModel> UserTableViewModels = []; List<UserTableViewModel> UserTableViewModels = [];
foreach (UserModel user in users) foreach (UserModel user in users)
{ {
string photo = user.JpegPhoto is not null && user.JpegPhoto.Length > 0 ? ImageHelper.ResizeAndConvertToBase64(Convert.FromBase64String(user.JpegPhoto), 32) : "";
UserTableViewModels.Add(new() UserTableViewModels.Add(new()
{ {
JpegPhoto = user.JpegPhoto ?? "", JpegPhoto = photo,
Name = user.Cn ?? "", Name = user.Cn ?? "",
Surname = user.Sn ?? "", Surname = user.Sn ?? "",
Title = user.Title ?? "", Title = user.Title ?? "",
@@ -95,6 +97,24 @@ public class HomeController : Controller
return View(new UsersIndexViewModel() { UserTableViewModels = UserTableViewModels }); return View(new UsersIndexViewModel() { UserTableViewModels = UserTableViewModels });
} }
[Authorize(Roles = "CanManageUsers")]
[HttpGet("UserPhoto")]
public async Task<IActionResult> UserPhotoAsync(string uid, int? size)
{
UserModel? user = await _ldap.GetUserByUidAsync(uid, _ldap.UsersAttributes);
if (user is null || user.JpegPhoto is null)
{
return NotFound();
}
if (size is null)
{
return File(Convert.FromBase64String(user.JpegPhoto), "image/jpeg");
}
string encodedFile = ImageHelper.ResizeAndConvertToBase64(Convert.FromBase64String(user.JpegPhoto), size ?? 32);
return File(Convert.FromBase64String(encodedFile), "image/jpeg");
}
[Authorize(Roles = "CanManageGroups")] [Authorize(Roles = "CanManageGroups")]
[HttpGet("Groups")] [HttpGet("Groups")]
public async Task<ActionResult> GroupsAsync() public async Task<ActionResult> GroupsAsync()

View File

@@ -0,0 +1,42 @@
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Jpeg;
using System.IO;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Webp;
namespace Berufsschule_HAM.Helpers;
public static class ImageHelper
{
public static string ResizeAndConvertToBase64(byte[] imageBytes, int size = 32)
{
size = Math.Min(size, 256);
using var inputStream = new MemoryStream(imageBytes);
using var image = Image.Load(inputStream);
// Optional: crop to square before resize
int minDimension = Math.Min(image.Width, image.Height);
var cropRectangle = new Rectangle(
(image.Width - minDimension) / 2,
(image.Height - minDimension) / 2,
minDimension,
minDimension);
image.Mutate(x =>
{
x.Crop(cropRectangle);
x.Resize(new ResizeOptions
{
Size = new Size(size, size),
Mode = ResizeMode.Crop
});
});
using var outputStream = new MemoryStream();
image.Save(outputStream, new WebpEncoder(){});
return Convert.ToBase64String(outputStream.ToArray());
}
}

View File

@@ -41,7 +41,7 @@
{ {
<tr class="user-row"> <tr class="user-row">
<td> <td>
<img class="rounded-circle user-icon" src="data:image/jpeg;base64,@userTableViewModel.JpegPhoto" alt="Photo" style="max-width:300px;" /> <img class="rounded-circle user-icon" src="~/Home/UserPhoto?uid=@userTableViewModel.Uid&size=48" alt="Photo" style="width:32px;height:32px;" width="32" height="32" loading="lazy" />
</td> </td>
<td>@userTableViewModel.Uid</td> <td>@userTableViewModel.Uid</td>
<td>@userTableViewModel.Title</td> <td>@userTableViewModel.Title</td>
@@ -730,13 +730,7 @@
// Photo // Photo
const imgEl = row.querySelector('td:first-child img'); const imgEl = row.querySelector('td:first-child img');
const detailPhoto = document.getElementById('detailPhoto'); const detailPhoto = document.getElementById('detailPhoto');
if (imgEl && imgEl.src.startsWith('data:image')) { detailPhoto.src = `/Home/UserPhoto?uid=${data.uid}&size=256`;
detailPhoto.src = imgEl.src;
detailPhoto.style.display = 'block';
} else {
detailPhoto.style.display = 'none';
}
detailModal.show(); detailModal.show();
}); });
}); });

View File

@@ -94,6 +94,9 @@ function idToEAN13(id) {
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const filters = document.querySelectorAll('.column-filter'); const filters = document.querySelectorAll('.column-filter');
const table = document.querySelector('table'); const table = document.querySelector('table');
if (table == null) {
return;
}
const tbody = table.querySelector('tbody'); const tbody = table.querySelector('tbody');
filters.forEach(filter => { filters.forEach(filter => {