From 918db73338e16c80cc1f0c0863a4cabc3a82e0f2 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 15:04:51 +0100 Subject: [PATCH 01/40] Fixed touch targets have insufficient spacing --- src/Views/Home/Groups.cshtml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Views/Home/Groups.cshtml b/src/Views/Home/Groups.cshtml index aa765f6..abd8a29 100644 --- a/src/Views/Home/Groups.cshtml +++ b/src/Views/Home/Groups.cshtml @@ -180,7 +180,7 @@
-
+
@@ -190,7 +190,7 @@
-
+
@@ -200,7 +200,7 @@
-
+
@@ -337,7 +337,7 @@
-
+
@@ -347,7 +347,7 @@
-
+
@@ -357,7 +357,7 @@
-
+
From d3a7bae53521a81561cfeaee27567e1440f6e960 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 15:05:08 +0100 Subject: [PATCH 02/40] Fixed wrong address in user settings view --- src/Views/Settings/User.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/Settings/User.cshtml b/src/Views/Settings/User.cshtml index e64adff..6029438 100644 --- a/src/Views/Settings/User.cshtml +++ b/src/Views/Settings/User.cshtml @@ -53,7 +53,7 @@
- +
From 377e8f6776dc7e2bfd32fd804eaf0d35ff137094 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 15:35:23 +0100 Subject: [PATCH 03/40] Fixed toast warning type not accessible --- src/wwwroot/js/site.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/wwwroot/js/site.js b/src/wwwroot/js/site.js index c1bd0c8..a30dea4 100644 --- a/src/wwwroot/js/site.js +++ b/src/wwwroot/js/site.js @@ -12,14 +12,19 @@ function showToast(message, type) { const toast = document.createElement('div'); toast.className = `toast align-items-center text-white bg-${type} border-0`; toast.role = 'alert'; + var useDarkElements = type === "warning" toast.innerHTML = `
${message}
- +
`; + if (useDarkElements) { + toast.classList.remove("text-white"); + toast.classList.add("text-dark"); + } toastContainer.appendChild(toast); - const bsToast = new bootstrap.Toast(toast, { delay: 3000 }); + const bsToast = new bootstrap.Toast(toast, { delay: 5000 }); bsToast.show(); toast.addEventListener('hidden.bs.toast', () => toast.remove()); } From a63323cc3b46e719e3bd9442b6de23c70b0878eb Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 15:35:44 +0100 Subject: [PATCH 04/40] Fixed toast warning type not accessible 2 --- src/Resources/Views.Shared._Layout.de.resx | 3 +++ src/Views/Shared/_Layout.cshtml | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Resources/Views.Shared._Layout.de.resx b/src/Resources/Views.Shared._Layout.de.resx index c522130..7e27699 100644 --- a/src/Resources/Views.Shared._Layout.de.resx +++ b/src/Resources/Views.Shared._Layout.de.resx @@ -52,6 +52,9 @@ Vorlage auswählen + + Meldung schließen + Einstellungen diff --git a/src/Views/Shared/_Layout.cshtml b/src/Views/Shared/_Layout.cshtml index c8471b6..554d24f 100644 --- a/src/Views/Shared/_Layout.cshtml +++ b/src/Views/Shared/_Layout.cshtml @@ -39,7 +39,8 @@ selectUser: '@T["Select user"]', selectPreset: '@T["Select preset"]', errorLoadingUsers: '@T["Error loading users"]', - errorLoadingPresets: '@T["Error loading presets"]' + errorLoadingPresets: '@T["Error loading presets"]', + closeAlert: '@T["Close alert"]' }; From 48f0eb0e7e016f632362600f7fc39f55d85a332a Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 21:47:03 +0100 Subject: [PATCH 05/40] Improved toast accessibility, improved batch accessibility --- src/Resources/Views.Shared._Batch.de.resx | 3 +++ src/Resources/Views.Shared._BatchButton.resx | 20 +++++++++++++++++++ src/Views/Shared/_Batch.cshtml | 18 +++++++++++++---- src/Views/Shared/_BatchButton.cshtml | 5 ++++- src/wwwroot/js/site.js | 21 ++++++++++++++++++-- 5 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 src/Resources/Views.Shared._BatchButton.resx diff --git a/src/Resources/Views.Shared._Batch.de.resx b/src/Resources/Views.Shared._Batch.de.resx index 23cc857..4898375 100644 --- a/src/Resources/Views.Shared._Batch.de.resx +++ b/src/Resources/Views.Shared._Batch.de.resx @@ -73,4 +73,7 @@ Seriennummer + + Eintrag löschen + diff --git a/src/Resources/Views.Shared._BatchButton.resx b/src/Resources/Views.Shared._BatchButton.resx new file mode 100644 index 0000000..ec9967a --- /dev/null +++ b/src/Resources/Views.Shared._BatchButton.resx @@ -0,0 +1,20 @@ + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, ... + + + System.Resources.ResXResourceWriter, System.Windows.Forms, ... + + + + Druckseite öffnen + + + diff --git a/src/Views/Shared/_Batch.cshtml b/src/Views/Shared/_Batch.cshtml index 2c8fc05..c6adfc6 100644 --- a/src/Views/Shared/_Batch.cshtml +++ b/src/Views/Shared/_Batch.cshtml @@ -67,7 +67,10 @@ const asset = json.assetsModel; assetCard.innerHTML = `
-

Asset ${i + 1}: ${asset.Name}

+ +

Asset ${i + 1}: ${asset.Name}

+ +

@T["Asset ID"]: ${asset.Cn}

@@ -87,7 +90,10 @@ } else { assetCard.innerHTML = `
-

Asset ${i + 1}: @T["Empty"]

+ +

Asset ${i + 1}: @T["Empty"]

+ +
@@ -236,10 +242,14 @@ let cardAtIndex = document.querySelector(`[data-card-index="${index}"]`) let cardBodyAtIndex = cardAtIndex.children[0]; - cardBodyAtIndex.children[0].children[1].textContent = `Asset ${newIndex + 1}`; + cardBodyAtIndex.children[1].children[0].children[0].children[0].nextSibling.textContent = ` ${newIndex + 1}`; + cardBodyAtIndex.children[0].children[0].children[0].textContent = `Asset ${newIndex + 1}:`; + cardBodyAtIndex.children[0].children[0].children[0].ariaLabel = cardBodyAtIndex.children[0].children[0].children[0].ariaLabel.replace(/\s\d+\:/, ` ${newIndex + 1}:`); let cardAtTarget = document.querySelector(`[data-card-index="${newIndex}"]`) let cardBodyAtTarget = cardAtTarget.children[0]; - cardBodyAtTarget.children[0].children[1].textContent = `Asset ${index - 0 + 1}`; + cardBodyAtTarget.children[1].children[0].children[0].children[0].nextSibling.textContent = ` ${index - 0 + 1}`; + cardBodyAtTarget.children[0].children[0].children[0].textContent = `Asset ${index - 0 + 1}:`; + cardBodyAtTarget.children[0].children[0].children[0].ariaLabel = cardBodyAtTarget.children[0].children[0].children[0].ariaLabel.replace(/\s\d+\:/, ` ${newIndex + 1}:`); cardAtIndex.insertBefore(cardBodyAtTarget, null); cardAtTarget.insertBefore(cardBodyAtIndex, null); diff --git a/src/Views/Shared/_BatchButton.cshtml b/src/Views/Shared/_BatchButton.cshtml index 5fe2fdc..91504c4 100644 --- a/src/Views/Shared/_BatchButton.cshtml +++ b/src/Views/Shared/_BatchButton.cshtml @@ -1,8 +1,11 @@ +@using Microsoft.AspNetCore.Mvc.Localization +@inject IViewLocalizer T + \ No newline at end of file diff --git a/src/wwwroot/js/site.js b/src/wwwroot/js/site.js index a30dea4..0fd7bda 100644 --- a/src/wwwroot/js/site.js +++ b/src/wwwroot/js/site.js @@ -2,6 +2,16 @@ const container = document.createElement('div'); container.id = 'toastContainer'; container.className = 'toast-container position-fixed bottom-0 end-0 p-3'; + container.setAttribute("aria-live", "polite"); + container.setAttribute("aria-atomic", "true"); + + const liveRegion = document.createElement('div'); + liveRegion.id = 'toastLiveRegion'; + liveRegion.className = 'visually-hidden'; + liveRegion.setAttribute('aria-live', 'assertive'); + liveRegion.setAttribute('aria-atomic', 'true'); + container.appendChild(liveRegion); + document.body.appendChild(container); return container; } @@ -16,7 +26,7 @@ function showToast(message, type) { toast.innerHTML = `
${message}
- +
`; if (useDarkElements) { @@ -24,7 +34,14 @@ function showToast(message, type) { toast.classList.add("text-dark"); } toastContainer.appendChild(toast); - const bsToast = new bootstrap.Toast(toast, { delay: 5000 }); + + const liveRegion = document.getElementById('toastLiveRegion'); + if (liveRegion) { + liveRegion.textContent = ''; + setTimeout(() => liveRegion.textContent = message, 500); + } + + const bsToast = new bootstrap.Toast(toast, { delay: 10000 }); bsToast.show(); toast.addEventListener('hidden.bs.toast', () => toast.remove()); } From 2b62e930e1ef39e1ccbf20b38d7ea7b903065374 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 22:15:36 +0100 Subject: [PATCH 06/40] Improved screenreader based navigation for groups view --- src/Resources/Views.Home.Groups.de.resx | 6 ++++ src/Views/Home/Groups.cshtml | 46 +++++++++++++------------ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/Resources/Views.Home.Groups.de.resx b/src/Resources/Views.Home.Groups.de.resx index 8314635..8d192f3 100644 --- a/src/Resources/Views.Home.Groups.de.resx +++ b/src/Resources/Views.Home.Groups.de.resx @@ -127,4 +127,10 @@ Fehler beim Anpassen der Gruppe + + Ja + + + Nein + diff --git a/src/Views/Home/Groups.cshtml b/src/Views/Home/Groups.cshtml index abd8a29..d908430 100644 --- a/src/Views/Home/Groups.cshtml +++ b/src/Views/Home/Groups.cshtml @@ -38,13 +38,13 @@ foreach (GroupsTableViewModel groupTableViewModel in Model.GroupsTableViewModels) { - @groupTableViewModel.Group - @(groupTableViewModel.CanInventorize ? "✓" : "✗") - @(groupTableViewModel.CanManageUsers ? "✓" : "✗") - @(groupTableViewModel.CanManageLocations ? "✓" : "✗") - @(groupTableViewModel.CanManageAssets ? "✓" : "✗") - @(groupTableViewModel.CanManageGroups ? "✓" : "✗") - @(groupTableViewModel.CanManageSettings ? "✓" : "✗") + @groupTableViewModel.Group + @T["inventorize"]@(groupTableViewModel.CanInventorize ? Html.Raw($"{@T["Yes"].Value}") : Html.Raw($"{@T["No"].Value}")) + @T["manage users"]@(groupTableViewModel.CanManageUsers ? Html.Raw($"{@T["Yes"].Value}") : Html.Raw($"{@T["No"].Value}")) + @T["manage locations"]@(groupTableViewModel.CanManageLocations ? Html.Raw($"{@T["Yes"].Value}") : Html.Raw($"{@T["No"].Value}")) + @T["manage assets"]@(groupTableViewModel.CanManageAssets ? Html.Raw($"{@T["Yes"].Value}") : Html.Raw($"{@T["No"].Value}")) + @T["manage groups"]@(groupTableViewModel.CanManageGroups ? Html.Raw($"{@T["Yes"].Value}") : Html.Raw($"{@T["No"].Value}")) + @T["manage settings"]@(groupTableViewModel.CanManageSettings ? Html.Raw($"{@T["Yes"].Value}") : Html.Raw($"{@T["No"].Value}"))
${asset.Description?.Attributes ? ` @@ -182,8 +182,8 @@ ${Object.entries(asset.Description.Attributes) .map(([k,v]) => `
- : - + : +
`) .join('')}
` : ''} @@ -194,19 +194,19 @@

@T["Purchase Information"]

- +
- +
- +
- +
` : ''}
`; From c337287b230d82100d48ac84766ee88d9d306352 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 22:39:43 +0100 Subject: [PATCH 08/40] Improved screenreader table discoverability --- src/Views/Home/Assets.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/Home/Assets.cshtml b/src/Views/Home/Assets.cshtml index dd4ff62..b11c162 100644 --- a/src/Views/Home/Assets.cshtml +++ b/src/Views/Home/Assets.cshtml @@ -49,7 +49,7 @@ foreach (AssetsTableViewModel assetsTableViewModel in Model.AssetsTableViewModels) { - @assetsTableViewModel.UserUID + @assetsTableViewModel.UserUID @assetsTableViewModel.AssetCn @assetsTableViewModel.AssetName @assetsTableViewModel.LocationName From 4f1c55d456aa59f94be60de7916a66523ccb96aa Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 22:45:27 +0100 Subject: [PATCH 09/40] Fixed assets view new items not screenreader navigable --- src/Views/Home/Assets.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/Home/Assets.cshtml b/src/Views/Home/Assets.cshtml index b11c162..ce30fe3 100644 --- a/src/Views/Home/Assets.cshtml +++ b/src/Views/Home/Assets.cshtml @@ -364,7 +364,7 @@ document.addEventListener('DOMContentLoaded', () => { if (tableBody) { const newRow = document.createElement('tr'); newRow.innerHTML = ` - ${jsonData.Owner || ''} + ${jsonData.Owner || ''} ${result.assetId || ''} ${jsonData.Name || ''} ${jsonData.Location || ''} From 647ca335ea02e3f29b03eca3a0c45de394c8e1b5 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 22:54:24 +0100 Subject: [PATCH 10/40] Improved locations screenreader navigation, fixed unable to delete location after editing --- src/Views/Home/Locations.cshtml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Views/Home/Locations.cshtml b/src/Views/Home/Locations.cshtml index d95bf4a..44f00f0 100644 --- a/src/Views/Home/Locations.cshtml +++ b/src/Views/Home/Locations.cshtml @@ -40,7 +40,7 @@ foreach (LocationTableViewModel locationTableViewModel in Model.LocationTableViewModels) { - @locationTableViewModel.LocationID + @locationTableViewModel.LocationID @locationTableViewModel.LocationName @locationTableViewModel.RoomNumber @locationTableViewModel.Seat @@ -234,6 +234,7 @@ const result = await response.json(); if (result) { const btn = document.querySelector(`button[data-location-id="${data.Location}"]`); + const btn_del = document.querySelector(`button.btn-danger[data-location-id="${data.Location}"]`); const row = btn.closest('tr'); let slugifiedLocationID = `${data.Description.Location}-${data.Description.RoomNumber}-${data.Description.Seat}` .toLowerCase() @@ -244,6 +245,10 @@ btn.setAttribute("data-location-name", data.Description.Location); btn.setAttribute("data-room-number", data.Description.RoomNumber); btn.setAttribute("data-seat", data.Description.Seat); + btn_del.setAttribute("data-location-id", slugifiedLocationID); + btn_del.setAttribute("data-location-name", data.Description.Location); + btn_del.setAttribute("data-room-number", data.Description.RoomNumber); + btn_del.setAttribute("data-seat", data.Description.Seat); row.children[0].textContent = slugifiedLocationID; row.children[1].textContent = data.Description.Location; row.children[2].textContent = data.Description.RoomNumber; @@ -339,7 +344,7 @@ const tbody = document.querySelector('table tbody'); const newRow = document.createElement('tr'); newRow.innerHTML = ` - ${slugifiedLocationID} + ${slugifiedLocationID} ${newLoc.Location} ${newLoc.RoomNumber} ${newLoc.Seat} From 17c9e46a5b3c569b1902122079bdde89d0ec2508 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 23:02:17 +0100 Subject: [PATCH 11/40] Improved users view screenreader table navigation --- src/Views/Home/Users.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 64e38f8..720e5a7 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -43,7 +43,7 @@ Photo - @userTableViewModel.Uid + @userTableViewModel.Uid @userTableViewModel.Title @userTableViewModel.Name @userTableViewModel.Surname @@ -557,7 +557,7 @@ const newRow = document.createElement('tr'); newRow.innerHTML = ` Photo - ${result.Uid || ''} + ${result.Uid || ''} ${data.Title || ''} ${data.Cn || ''} ${data.Sn || ''} From 22d72e124f22cdcac5ee8cb51ab5afb87a518462 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Sun, 23 Nov 2025 23:44:54 +0100 Subject: [PATCH 12/40] Improved admin settings view screenreader navigation --- src/Resources/Views.Settings.Admin.de.resx | 3 ++ src/Views/Settings/Admin.cshtml | 32 +++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Resources/Views.Settings.Admin.de.resx b/src/Resources/Views.Settings.Admin.de.resx index b8644ee..1e716dc 100644 --- a/src/Resources/Views.Settings.Admin.de.resx +++ b/src/Resources/Views.Settings.Admin.de.resx @@ -110,4 +110,7 @@ Cache leeren + + Vorlagen. Klicken zum Auf- oder zuklappen + diff --git a/src/Views/Settings/Admin.cshtml b/src/Views/Settings/Admin.cshtml index 79ea760..3bc1e39 100644 --- a/src/Views/Settings/Admin.cshtml +++ b/src/Views/Settings/Admin.cshtml @@ -68,14 +68,18 @@
-

@T["Current user image cache utilization:"] @userImageCacheSize

+

@T["Current user image cache utilization:"] @userImageCacheSize

+ style="cursor: pointer;" + role="button" + tabindex="0" + aria-controls="presetsCollapse" + aria-label="@T["Presets. Press to toggle visibility"]">

@T["Presets"]

{ + + const toggle = document.querySelector('[data-bs-target="#presetsCollapse"]'); + + toggle.addEventListener("keydown", (e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + toggle.click(); + + const collapse = document.querySelector("#presetsCollapse"); + const isShown = collapse.classList.contains("show"); + toggle.setAttribute("aria-expanded", isShown ? "true" : "false"); + } + }); + + toggle.addEventListener("click", () => { + const collapse = document.querySelector("#presetsCollapse"); + const isShown = collapse.classList.contains("show"); + toggle.setAttribute("aria-expanded", isShown ? "true" : "false"); + }); + const updateForm = document.getElementById('updateSettings'); updateForm.addEventListener('submit', async e => { e.preventDefault(); @@ -375,10 +399,10 @@
- +
- +
From e9a55761c259ca61bd5e098c765136d1e9e757ea Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Mon, 24 Nov 2025 11:02:58 +0100 Subject: [PATCH 13/40] Improved LdapService thread safety, fixed user images sometimes not loading --- src/Controllers/HomeController.cs | 3 +- src/Services/LdapService.cs | 92 +++++++++++++++++++++++-------- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/Controllers/HomeController.cs b/src/Controllers/HomeController.cs index fc3e440..a4a467a 100644 --- a/src/Controllers/HomeController.cs +++ b/src/Controllers/HomeController.cs @@ -98,7 +98,6 @@ public class HomeController : Controller [ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new[] { "uid", "size" })] public async Task UserPhotoAsync(string uid, int? size) { - Task adminSettingsModelTask = _ldap.GetAdminSettingsModelAsync(); UserModel? user = await _ldap.GetUserByUidAsync(uid, _ldap.UsersAttributes); if (user is null || user.JpegPhoto is null || user.JpegPhoto == "") { @@ -110,7 +109,7 @@ public class HomeController : Controller } if (size is not null) { - AdminSettingsModel adminSettingsModel = await adminSettingsModelTask; + AdminSettingsModel adminSettingsModel = await _ldap.GetAdminSettingsModelAsync(); size = Math.Min((int)size, adminSettingsModel.MaxDownloadableUserImageSize); } byte[] encodedFile = ImageHelper.ResizeAndConvertToWebp(user.JpegPhoto, size ?? 32); diff --git a/src/Services/LdapService.cs b/src/Services/LdapService.cs index bb74a51..17a3f6a 100644 --- a/src/Services/LdapService.cs +++ b/src/Services/LdapService.cs @@ -11,6 +11,7 @@ public partial class LdapService : IDisposable { private readonly LdapConfig _opts; private readonly LdapConnection _conn; + private readonly SemaphoreSlim _connLock = new(1, 1); private AdminSettingsModel? adminSettingsModel; private ILogger _logger; @@ -29,19 +30,27 @@ public partial class LdapService : IDisposable { try { - if (!_conn.Connected) + await _connLock.WaitAsync(); + try { - try + if (!_conn.Connected) { - await _conn.ConnectAsync(_opts.Host, _opts.Port); - } - catch (SystemException ex) - { - _logger.LogWarning("Unable to connect to LDAP: {ex.Message}\n{ex.StackTrace}", [ex.Message, ex.StackTrace]); - throw; + try + { + await _conn.ConnectAsync(_opts.Host, _opts.Port); + } + catch (SystemException ex) + { + _logger.LogWarning("Unable to connect to LDAP: {ex.Message}\n{ex.StackTrace}", [ex.Message, ex.StackTrace]); + throw; + } } + await _conn.BindAsync(_opts.BindDn, _opts.BindPassword); + } + finally + { + _connLock.Release(); } - await _conn.BindAsync(_opts.BindDn, _opts.BindPassword); return; } catch (Exception ex) @@ -109,7 +118,7 @@ public partial class LdapService : IDisposable LdapModification.Replace, new LdapAttribute("description", targetText) ); - await _conn.ModifyAsync(dn, modification); + await ModifyAsync(dn, modification); } catch (Exception) { @@ -151,7 +160,7 @@ public partial class LdapService : IDisposable LdapModification.Replace, new LdapAttribute("description", targetText) ); - await _conn.ModifyAsync(dn, modification); + await ModifyAsync(dn, modification); } catch (Exception) { @@ -201,7 +210,7 @@ public partial class LdapService : IDisposable LdapModification.Replace, new LdapAttribute("description", targetText) ); - await _conn.ModifyAsync(dn, modification); + await ModifyAsync(dn, modification); } catch (Exception) { @@ -388,15 +397,16 @@ public async Task CreateAsset(LdapAttributeSet attributeSet) public async Task>> ListObjectBy(string baseDn, string filter, string[] attributes) { - return await Task.Run(async () => + await ConnectAndBind(); + await _connLock.WaitAsync(); + try { - await ConnectAndBind(); var search = await _conn.SearchAsync( baseDn, LdapConnection.ScopeSub, $"{filter}", attributes, - false); + false).ConfigureAwait(false); var list = new List>(); while (await search.HasMoreAsync()) { @@ -415,7 +425,11 @@ public async Task CreateAsset(LdapAttributeSet attributeSet) catch (LdapException) { } } return list; - }); + } + finally + { + _connLock.Release(); + } } public async Task DeleteUserAsync(string uid) @@ -468,7 +482,15 @@ public async Task CreateAsset(LdapAttributeSet attributeSet) string dn = PrependRDN($"{rdnKey}={rdnValue}", baseDn); if (attributeName == rdnKey) { - await _conn.RenameAsync(dn, $"{rdnKey}={attributeValue}", true); + await _connLock.WaitAsync(); + try + { + await _conn.RenameAsync(dn, $"{rdnKey}={attributeValue}", true); + } + finally + { + _connLock.Release(); + } } else { @@ -476,7 +498,7 @@ public async Task CreateAsset(LdapAttributeSet attributeSet) LdapModification.Replace, new LdapAttribute(attributeName, attributeValue) ); - await _conn.ModifyAsync(dn, modification); + await ModifyAsync(dn, modification); } } @@ -490,24 +512,48 @@ public async Task CreateAsset(LdapAttributeSet attributeSet) new LdapAttribute(attributeName) ); - await _conn.ModifyAsync(dn, modification); + await ModifyAsync(dn, modification); } public async Task DeleteObjectByDnAsync(string dn) { - await _conn.DeleteAsync(dn); + await _connLock.WaitAsync(); + try + { + await _conn.DeleteAsync(dn); + } + finally + { + _connLock.Release(); + } } public async Task CreateObject(string dn, LdapAttributeSet attributeSet) { await ConnectAndBind(); LdapEntry ldapEntry = new(dn, attributeSet); - await _conn.AddAsync(ldapEntry); + await _connLock.WaitAsync(); + try + { + await _conn.AddAsync(ldapEntry); + } + finally + { + _connLock.Release(); + } } - public async Task ModifyAsync(string dn, LdapModification ldapModification) + public async Task ModifyAsync(string dn, LdapModification mod, CancellationToken ct = default) { - await _conn.ModifyAsync(dn, ldapModification); + await _connLock.WaitAsync(ct); + try + { + await _conn.ModifyAsync(dn, mod, ct); + } + finally + { + _connLock.Release(); + } } public void Dispose() From 3f6ecbe6174408e17bbe7c1dbdc01db1cdcdd6ff Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Mon, 24 Nov 2025 11:06:45 +0100 Subject: [PATCH 14/40] Fixed newly created user can't be deleted --- src/Views/Home/Users.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 720e5a7..5feb539 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -580,7 +580,7 @@ @T["Update"]
- +

@T["Workplace & account"]

@@ -213,7 +213,7 @@
- +
@@ -278,6 +278,9 @@ var dataFromEntries = Object.fromEntries(new FormData(updateForm).entries()); var data = unflatten(dataFromEntries); data.Description.Groups = Array.from(updateForm.querySelector('#updateGroups').selectedOptions).map(option => option.value); + if (data.Description.Address.StreetNr == "") { + delete(data.Description.Address.StreetNr); + } try { const response = await fetch('/Users/Update', { method: 'POST', @@ -435,11 +438,11 @@
- +
- +
@@ -455,7 +458,7 @@
- +

@T["Workplace & account"]

@@ -471,7 +474,7 @@
- +
@@ -539,7 +542,9 @@ const dataFromEntries = Object.fromEntries(new FormData(createForm).entries()); const data = unflatten(dataFromEntries); data.Description.Groups = Array.from(createGroupsSelect.selectedOptions).map(o => o.value); - + if (data.Description.Address.StreetNr == "") { + delete(data.Description.Address.StreetNr); + } try { const response = await fetch('/Users/Create', { method: 'POST', @@ -572,7 +577,7 @@ data-user-birthdate="${data.Description.BirthDate}" data-user-address-city="${data.Description.Address.City}" data-user-address-street="${data.Description.Address.Street}" - data-user-address-streetnr="${data.Description.Address.StreetNr}" + data-user-address-streetnr="${data.Description.Address.StreetNr || ""}" data-user-workplace="${data.Description?.Workplace || ''}" data-user-groups='${JSON.stringify(data.Description?.Groups || [])}' data-bs-toggle="modal" From feefa29cdf3775b3667935bcb9aaac8ae40b6b2e Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Mon, 24 Nov 2025 13:17:02 +0100 Subject: [PATCH 16/40] Added missing required fields and aria-required to users view --- src/Views/Home/Users.cshtml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 96f594c..8deb724 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -176,12 +176,12 @@
- - + +
- - + +
@@ -437,12 +437,12 @@
- - + +
- - + +
@@ -473,8 +473,8 @@
- - + +
From 53ad5526c7c85b76c297378335a29bbfbfa52575 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Mon, 24 Nov 2025 13:18:15 +0100 Subject: [PATCH 17/40] Removed superfluous spaces --- src/Views/Home/Users.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Views/Home/Users.cshtml b/src/Views/Home/Users.cshtml index 8deb724..87dd233 100644 --- a/src/Views/Home/Users.cshtml +++ b/src/Views/Home/Users.cshtml @@ -176,11 +176,11 @@
- +
- +
From 7c77d10fea45014f147479858e8b1735cb083cc9 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Mon, 24 Nov 2025 13:20:05 +0100 Subject: [PATCH 18/40] Added missing required indication and aria-required to locations view --- src/Views/Home/Locations.cshtml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Views/Home/Locations.cshtml b/src/Views/Home/Locations.cshtml index 44f00f0..8aeba0d 100644 --- a/src/Views/Home/Locations.cshtml +++ b/src/Views/Home/Locations.cshtml @@ -165,8 +165,8 @@
- - + +
@@ -281,8 +281,8 @@