mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
Merge pull request #345 from LD-Reborn/340-chore-wcag-22-aa-check
340 chore wcag 22 aa check
This commit is contained in:
@@ -98,7 +98,6 @@ public class HomeController : Controller
|
|||||||
[ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new[] { "uid", "size" })]
|
[ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new[] { "uid", "size" })]
|
||||||
public async Task<IActionResult> UserPhotoAsync(string uid, int? size)
|
public async Task<IActionResult> UserPhotoAsync(string uid, int? size)
|
||||||
{
|
{
|
||||||
Task<AdminSettingsModel> adminSettingsModelTask = _ldap.GetAdminSettingsModelAsync();
|
|
||||||
UserModel? user = await _ldap.GetUserByUidAsync(uid, _ldap.UsersAttributes);
|
UserModel? user = await _ldap.GetUserByUidAsync(uid, _ldap.UsersAttributes);
|
||||||
if (user is null || user.JpegPhoto is null || user.JpegPhoto == "")
|
if (user is null || user.JpegPhoto is null || user.JpegPhoto == "")
|
||||||
{
|
{
|
||||||
@@ -110,7 +109,7 @@ public class HomeController : Controller
|
|||||||
}
|
}
|
||||||
if (size is not null)
|
if (size is not null)
|
||||||
{
|
{
|
||||||
AdminSettingsModel adminSettingsModel = await adminSettingsModelTask;
|
AdminSettingsModel adminSettingsModel = await _ldap.GetAdminSettingsModelAsync();
|
||||||
size = Math.Min((int)size, adminSettingsModel.MaxDownloadableUserImageSize);
|
size = Math.Min((int)size, adminSettingsModel.MaxDownloadableUserImageSize);
|
||||||
}
|
}
|
||||||
byte[] encodedFile = ImageHelper.ResizeAndConvertToWebp(user.JpegPhoto, size ?? 32);
|
byte[] encodedFile = ImageHelper.ResizeAndConvertToWebp(user.JpegPhoto, size ?? 32);
|
||||||
@@ -167,6 +166,7 @@ public class HomeController : Controller
|
|||||||
|
|
||||||
return RedirectToAction("Index", "Home");
|
return RedirectToAction("Index", "Home");
|
||||||
}
|
}
|
||||||
|
Response.StatusCode = 500;
|
||||||
switch (authenticationResult.AuthenticationState)
|
switch (authenticationResult.AuthenticationState)
|
||||||
{
|
{
|
||||||
case UserNotAuthenticatedReason.InvalidCredentials:
|
case UserNotAuthenticatedReason.InvalidCredentials:
|
||||||
@@ -194,9 +194,29 @@ public class HomeController : Controller
|
|||||||
return RedirectToAction("Index", "Home");
|
return RedirectToAction("Index", "Home");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("Accessibility")]
|
||||||
|
public ActionResult Accessibility()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("AccessDenied")]
|
[HttpGet("AccessDenied")]
|
||||||
public ActionResult AccessDenied()
|
public ActionResult AccessDenied()
|
||||||
{
|
{
|
||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpGet("RemainingTime")]
|
||||||
|
public async Task<IActionResult> GetRemainingSessionTime()
|
||||||
|
{
|
||||||
|
var result = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||||
|
|
||||||
|
if (!result.Succeeded || result.Properties?.ExpiresUtc == null)
|
||||||
|
return Json(new { remainingMinutes = 0 });
|
||||||
|
|
||||||
|
var remaining = result.Properties.ExpiresUtc.Value - DateTimeOffset.UtcNow;
|
||||||
|
return Json(new { remainingMinutes = (int) Math.Ceiling(remaining.TotalMinutes) });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,6 @@ public class UsersController : Controller
|
|||||||
UserDescription? description = requestModel.Description;
|
UserDescription? description = requestModel.Description;
|
||||||
jpegPhoto ??= ImageHelper.GetDefaultUserImageAsBase64();
|
jpegPhoto ??= ImageHelper.GetDefaultUserImageAsBase64();
|
||||||
string uid = UsersHelper.CreateUsername(requestModel.Cn ?? "", requestModel.Sn ?? "");
|
string uid = UsersHelper.CreateUsername(requestModel.Cn ?? "", requestModel.Sn ?? "");
|
||||||
title ??= "";
|
|
||||||
description ??= new() {Address = new(), BirthDate = "", Workplace = "", Groups = []};
|
description ??= new() {Address = new(), BirthDate = "", Workplace = "", Groups = []};
|
||||||
if (!userPassword.StartsWith('{'))
|
if (!userPassword.StartsWith('{'))
|
||||||
{
|
{
|
||||||
@@ -88,12 +87,13 @@ public class UsersController : Controller
|
|||||||
new LdapAttribute("objectClass", "inetOrgPerson"),
|
new LdapAttribute("objectClass", "inetOrgPerson"),
|
||||||
new LdapAttribute("cn", requestModel.Cn),
|
new LdapAttribute("cn", requestModel.Cn),
|
||||||
new LdapAttribute("sn", requestModel.Sn),
|
new LdapAttribute("sn", requestModel.Sn),
|
||||||
new LdapAttribute("title", title),
|
|
||||||
new LdapAttribute("uid", uid),
|
new LdapAttribute("uid", uid),
|
||||||
new LdapAttribute("jpegPhoto", jpegPhoto),
|
new LdapAttribute("jpegPhoto", jpegPhoto),
|
||||||
new LdapAttribute("description", JsonSerializer.Serialize(description)),
|
new LdapAttribute("description", JsonSerializer.Serialize(description)),
|
||||||
new LdapAttribute("userPassword", userPassword),
|
new LdapAttribute("userPassword", userPassword),
|
||||||
];
|
];
|
||||||
|
if (title is not null && title.Length > 0) attributeSet.Add(new LdapAttribute("title", title));
|
||||||
|
|
||||||
await _ldap.CreateUser(uid, attributeSet);
|
await _ldap.CreateUser(uid, attributeSet);
|
||||||
return new(){Success = true, Uid = uid};
|
return new(){Success = true, Uid = uid};
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ public class UsersController : Controller
|
|||||||
await _ldap.UpdateUser(uid, "uid", requestModel.NewUid);
|
await _ldap.UpdateUser(uid, "uid", requestModel.NewUid);
|
||||||
uid = requestModel.NewUid;
|
uid = requestModel.NewUid;
|
||||||
}
|
}
|
||||||
if (requestModel.Title is not null)
|
if (requestModel.Title is not null && requestModel.Title.Length > 0)
|
||||||
{
|
{
|
||||||
await _ldap.UpdateUser(uid, "title", requestModel.Title);
|
await _ldap.UpdateUser(uid, "title", requestModel.Title);
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/Resources/Views.Home.Accessibility.de.resx
Normal file
41
src/Resources/Views.Home.Accessibility.de.resx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value>
|
||||||
|
</resheader>
|
||||||
|
|
||||||
|
<data name="Accessibility" xml:space="preserve">
|
||||||
|
<value>Barrierefreiheit</value>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<data name="PageIntro" xml:space="preserve">
|
||||||
|
<value>Am {0} entspricht sämtlicher Inhalt im Projekt LD-Reborn/HAM den Web Content Accessibility Guidelines 2.2 unter {1}. Konformitätsstufe Triple-A.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ReliesUpon" xml:space="preserve">
|
||||||
|
<value>Die Technologie, auf die sich dieser Inhalt "<a>stützt</a>", ist:</value>
|
||||||
|
</data>
|
||||||
|
<data name="UsesButNotReliesUpon" xml:space="preserve">
|
||||||
|
<value>Die Technologien, die dieser Inhalt <strong>verwendet, jedoch nicht voraussetzt</strong>, sind:</value>
|
||||||
|
</data>
|
||||||
|
<data name="TestedWith" xml:space="preserve">
|
||||||
|
<value>Dieser Inhalt wurde mit den folgenden User Agents und Assistenztechnologien getestet:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Features" xml:space="preserve">
|
||||||
|
<value>Diese Seite enthält sowohl <span property="accessMode" content="textual">Text</span>
|
||||||
|
als auch <span property="accessMode" content="visual">Bilder</span>.
|
||||||
|
<span property="accessibilityFeature" content="alternativeText">Alternativtexte</span> sind für alle Bilder enthalten, und <span
|
||||||
|
property="accessibilityFeature" content="longDescription">ausführliche Beschreibungen</span> werden für Bilder bereitgestellt, die mehr als einfachen Alternativtext erfordern. Sämtliche Inhalte sind in Textform verfügbar und können von Assistenztechnologien ausgelesen werden.</value>
|
||||||
|
</data>
|
||||||
|
<data name="OnWith" xml:space="preserve">
|
||||||
|
<value>{0} mit {1} und {2}</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
47
src/Resources/Views.Home.Accessibility.en.resx
Normal file
47
src/Resources/Views.Home.Accessibility.en.resx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value>
|
||||||
|
</resheader>
|
||||||
|
|
||||||
|
<data name="Accessibility" xml:space="preserve">
|
||||||
|
<value>Accessibility</value>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<data name="PageIntro" xml:space="preserve">
|
||||||
|
<value>On {0}, all content available in the project LD-Reborn/HAM conforms to Web Content Accessibility Guidelines 2.2 at {1}. Triple-A conformance.</value>
|
||||||
|
</data>
|
||||||
|
<data name="ReliesUpon" xml:space="preserve">
|
||||||
|
<value>The technology that this content "<a>relies upon</a>" is:</value>
|
||||||
|
</data>
|
||||||
|
<data name="UsesButNotReliesUpon" xml:space="preserve">
|
||||||
|
<value>The technologies that this content "<strong>uses but does not rely
|
||||||
|
upon</strong>" are:</value>
|
||||||
|
</data>
|
||||||
|
<data name="TestedWith" xml:space="preserve">
|
||||||
|
<value>This content was tested using the following user agents and assistive
|
||||||
|
technologies:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Features" xml:space="preserve">
|
||||||
|
<value>This page includes both <span property="accessMode" content="textual">text</span>
|
||||||
|
and <span property="accessMode" content="visual">images</span>.
|
||||||
|
<span property="accessibilityFeature" content="alternativeText">Alternative
|
||||||
|
text</span> is included for all image content and <span
|
||||||
|
property="accessibilityFeature" content="longDescription">long
|
||||||
|
descriptions</span> are also provided for images that require more
|
||||||
|
than simple alternate text. All content is available in text, which
|
||||||
|
can be accessed by assistive technology.</value>
|
||||||
|
</data>
|
||||||
|
<data name="OnWith" xml:space="preserve">
|
||||||
|
<value>{0} on {1} with {2}</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -184,4 +184,7 @@
|
|||||||
<data name="Apply preset" xml:space="preserve">
|
<data name="Apply preset" xml:space="preserve">
|
||||||
<value>Vorlage anwenden</value>
|
<value>Vorlage anwenden</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Jump to add attribute button" xml:space="preserve">
|
||||||
|
<value>Zu Attribut Hinzufügen Button springen</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -127,4 +127,10 @@
|
|||||||
<data name="Error updating group" xml:space="preserve">
|
<data name="Error updating group" xml:space="preserve">
|
||||||
<value>Fehler beim Anpassen der Gruppe</value>
|
<value>Fehler beim Anpassen der Gruppe</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Yes" xml:space="preserve">
|
||||||
|
<value>Ja</value>
|
||||||
|
</data>
|
||||||
|
<data name="No" xml:space="preserve">
|
||||||
|
<value>Nein</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -40,5 +40,10 @@
|
|||||||
<data name="Navigate from here or the navigation bar" xml:space="preserve">
|
<data name="Navigate from here or the navigation bar" xml:space="preserve">
|
||||||
<value>Navigiere von hier oder über die Navigationsleiste</value>
|
<value>Navigiere von hier oder über die Navigationsleiste</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="User settings" xml:space="preserve">
|
||||||
|
<value>Einstellungen</value>
|
||||||
|
</data>
|
||||||
|
<data name="Admin settings" xml:space="preserve">
|
||||||
|
<value>Administration</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -150,4 +150,7 @@
|
|||||||
<data name="Successfully added barcode to print batch" xml:space="preserve">
|
<data name="Successfully added barcode to print batch" xml:space="preserve">
|
||||||
<value>Barcode wurde erfolgreich dem Auftragsstapel hinzugefügt</value>
|
<value>Barcode wurde erfolgreich dem Auftragsstapel hinzugefügt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Jump to add attribute button" xml:space="preserve">
|
||||||
|
<value>Zu Attribut Hinzufügen Button springen</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -19,12 +19,6 @@
|
|||||||
<data name="Login" xml:space="preserve">
|
<data name="Login" xml:space="preserve">
|
||||||
<value>Anmelden</value>
|
<value>Anmelden</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Username" xml:space="preserve">
|
|
||||||
<value>Benutzername</value>
|
|
||||||
</data>
|
|
||||||
<data name="Password" xml:space="preserve">
|
|
||||||
<value>Passwort</value>
|
|
||||||
</data>
|
|
||||||
<data name="Invalid login credentials" xml:space="preserve">
|
<data name="Invalid login credentials" xml:space="preserve">
|
||||||
<value>Ungültige Anmeldedaten</value>
|
<value>Ungültige Anmeldedaten</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -110,4 +110,10 @@
|
|||||||
<data name="Clear user image cache" xml:space="preserve">
|
<data name="Clear user image cache" xml:space="preserve">
|
||||||
<value>Cache leeren</value>
|
<value>Cache leeren</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Presets. Press to toggle visibility" xml:space="preserve">
|
||||||
|
<value>Vorlagen. Klicken zum Auf- oder zuklappen</value>
|
||||||
|
</data>
|
||||||
|
<data name="Skip attributes table" xml:space="preserve">
|
||||||
|
<value>Attribut-Tabelle überspringen</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -64,4 +64,7 @@
|
|||||||
<data name="Error contacting server" xml:space="preserve">
|
<data name="Error contacting server" xml:space="preserve">
|
||||||
<value>Fehler bei der Kommunikation mit dem Server</value>
|
<value>Fehler bei der Kommunikation mit dem Server</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="There are no changes to be saved." xml:space="preserve">
|
||||||
|
<value>Es wurden keine Änderungen gemacht.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
@@ -73,4 +73,7 @@
|
|||||||
<data name="Serial" xml:space="preserve">
|
<data name="Serial" xml:space="preserve">
|
||||||
<value>Seriennummer</value>
|
<value>Seriennummer</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Delete entry" xml:space="preserve">
|
||||||
|
<value>Eintrag löschen</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
20
src/Resources/Views.Shared._BatchButton.resx
Normal file
20
src/Resources/Views.Shared._BatchButton.resx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value>
|
||||||
|
</resheader>
|
||||||
|
|
||||||
|
<data name="Open Print Page" xml:space="preserve">
|
||||||
|
<value>Druckseite öffnen</value>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
</root>
|
||||||
@@ -37,6 +37,9 @@
|
|||||||
<data name="Login" xml:space="preserve">
|
<data name="Login" xml:space="preserve">
|
||||||
<value>Anmelden</value>
|
<value>Anmelden</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Accessibility" xml:space="preserve">
|
||||||
|
<value>Barrierefreiheit</value>
|
||||||
|
</data>
|
||||||
<data name="Select location" xml:space="preserve">
|
<data name="Select location" xml:space="preserve">
|
||||||
<value>Ort auswählen</value>
|
<value>Ort auswählen</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -52,10 +55,37 @@
|
|||||||
<data name="Select preset" xml:space="preserve">
|
<data name="Select preset" xml:space="preserve">
|
||||||
<value>Vorlage auswählen</value>
|
<value>Vorlage auswählen</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Close alert" xml:space="preserve">
|
||||||
|
<value>Meldung schließen</value>
|
||||||
|
</data>
|
||||||
<data name="User settings" xml:space="preserve">
|
<data name="User settings" xml:space="preserve">
|
||||||
<value>Einstellungen</value>
|
<value>Einstellungen</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Admin settings" xml:space="preserve">
|
<data name="Admin settings" xml:space="preserve">
|
||||||
<value>Administration</value>
|
<value>Administration</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Extend session duration" xml:space="preserve">
|
||||||
|
<value>Sitzung verlängern</value>
|
||||||
|
</data>
|
||||||
|
<data name="The session expires soon." xml:space="preserve">
|
||||||
|
<value>Die Sitzung läuft bald ab.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Please authenticate to continue without losing data." xml:space="preserve">
|
||||||
|
<value>Bitte authentifizieren Sie sich, um fortzufahren, ohne Daten zu verlieren.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Re-authenticate" xml:space="preserve">
|
||||||
|
<value>Authentifizieren</value>
|
||||||
|
</data>
|
||||||
|
<data name="Invalid login credentials" xml:space="preserve">
|
||||||
|
<value>Ungültige Anmeldedaten</value>
|
||||||
|
</data>
|
||||||
|
<data name="Your account has been locked. Wait a few minutes or ask an administrator to unlock you" xml:space="preserve">
|
||||||
|
<value>Ihr Konto wurde gesperrt. Warten Sie einige Minuten oder bitten Sie einen Administrator, die Sperre aufzuheben.</value>
|
||||||
|
</data>
|
||||||
|
<data name="You are not authorized for login. Ask an administrator to authorize you." xml:space="preserve">
|
||||||
|
<value>Sie sind nicht zur Anmeldung berechtigt. Bitten Sie einen Administrator, Ihnen die Berechtigung zu erteilen.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Hell froze over. Make a screenshot and send it to an administrator." xml:space="preserve">
|
||||||
|
<value>Die Hölle ist zugefroren. Machen Sie einen Screenshot und senden Sie ihn an einen Administrator.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
|
|||||||
25
src/Resources/Views.Shared._Login.de.resx
Normal file
25
src/Resources/Views.Shared._Login.de.resx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, ...</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, ...</value>
|
||||||
|
</resheader>
|
||||||
|
|
||||||
|
<data name="Login" xml:space="preserve">
|
||||||
|
<value>Anmelden</value>
|
||||||
|
</data>
|
||||||
|
<data name="Username" xml:space="preserve">
|
||||||
|
<value>Benutzername</value>
|
||||||
|
</data>
|
||||||
|
<data name="Password" xml:space="preserve">
|
||||||
|
<value>Passwort</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
||||||
@@ -11,6 +11,7 @@ public partial class LdapService : IDisposable
|
|||||||
{
|
{
|
||||||
private readonly LdapConfig _opts;
|
private readonly LdapConfig _opts;
|
||||||
private readonly LdapConnection _conn;
|
private readonly LdapConnection _conn;
|
||||||
|
private readonly SemaphoreSlim _connLock = new(1, 1);
|
||||||
private AdminSettingsModel? adminSettingsModel;
|
private AdminSettingsModel? adminSettingsModel;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
|
||||||
@@ -29,19 +30,27 @@ public partial class LdapService : IDisposable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_conn.Connected)
|
await _connLock.WaitAsync();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
if (!_conn.Connected)
|
||||||
{
|
{
|
||||||
await _conn.ConnectAsync(_opts.Host, _opts.Port);
|
try
|
||||||
}
|
{
|
||||||
catch (SystemException ex)
|
await _conn.ConnectAsync(_opts.Host, _opts.Port);
|
||||||
{
|
}
|
||||||
_logger.LogWarning("Unable to connect to LDAP: {ex.Message}\n{ex.StackTrace}", [ex.Message, ex.StackTrace]);
|
catch (SystemException ex)
|
||||||
throw;
|
{
|
||||||
|
_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;
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -109,7 +118,7 @@ public partial class LdapService : IDisposable
|
|||||||
LdapModification.Replace,
|
LdapModification.Replace,
|
||||||
new LdapAttribute("description", targetText)
|
new LdapAttribute("description", targetText)
|
||||||
);
|
);
|
||||||
await _conn.ModifyAsync(dn, modification);
|
await ModifyAsync(dn, modification);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -151,7 +160,7 @@ public partial class LdapService : IDisposable
|
|||||||
LdapModification.Replace,
|
LdapModification.Replace,
|
||||||
new LdapAttribute("description", targetText)
|
new LdapAttribute("description", targetText)
|
||||||
);
|
);
|
||||||
await _conn.ModifyAsync(dn, modification);
|
await ModifyAsync(dn, modification);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -201,7 +210,7 @@ public partial class LdapService : IDisposable
|
|||||||
LdapModification.Replace,
|
LdapModification.Replace,
|
||||||
new LdapAttribute("description", targetText)
|
new LdapAttribute("description", targetText)
|
||||||
);
|
);
|
||||||
await _conn.ModifyAsync(dn, modification);
|
await ModifyAsync(dn, modification);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -388,15 +397,16 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
|
|||||||
|
|
||||||
public async Task<IEnumerable<Dictionary<string, string>>> ListObjectBy(string baseDn, string filter, string[] attributes)
|
public async Task<IEnumerable<Dictionary<string, string>>> 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(
|
var search = await _conn.SearchAsync(
|
||||||
baseDn,
|
baseDn,
|
||||||
LdapConnection.ScopeSub,
|
LdapConnection.ScopeSub,
|
||||||
$"{filter}",
|
$"{filter}",
|
||||||
attributes,
|
attributes,
|
||||||
false);
|
false).ConfigureAwait(false);
|
||||||
var list = new List<Dictionary<string, string>>();
|
var list = new List<Dictionary<string, string>>();
|
||||||
while (await search.HasMoreAsync())
|
while (await search.HasMoreAsync())
|
||||||
{
|
{
|
||||||
@@ -415,7 +425,11 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
|
|||||||
catch (LdapException) { }
|
catch (LdapException) { }
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
});
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_connLock.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteUserAsync(string uid)
|
public async Task DeleteUserAsync(string uid)
|
||||||
@@ -468,7 +482,15 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
|
|||||||
string dn = PrependRDN($"{rdnKey}={rdnValue}", baseDn);
|
string dn = PrependRDN($"{rdnKey}={rdnValue}", baseDn);
|
||||||
if (attributeName == rdnKey)
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -476,7 +498,7 @@ public async Task CreateAsset(LdapAttributeSet attributeSet)
|
|||||||
LdapModification.Replace,
|
LdapModification.Replace,
|
||||||
new LdapAttribute(attributeName, attributeValue)
|
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)
|
new LdapAttribute(attributeName)
|
||||||
);
|
);
|
||||||
|
|
||||||
await _conn.ModifyAsync(dn, modification);
|
await ModifyAsync(dn, modification);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteObjectByDnAsync(string dn)
|
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)
|
public async Task CreateObject(string dn, LdapAttributeSet attributeSet)
|
||||||
{
|
{
|
||||||
await ConnectAndBind();
|
await ConnectAndBind();
|
||||||
LdapEntry ldapEntry = new(dn, attributeSet);
|
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()
|
public void Dispose()
|
||||||
|
|||||||
22
src/Views/Home/Accessibility.cshtml
Normal file
22
src/Views/Home/Accessibility.cshtml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@using Microsoft.AspNetCore.Html
|
||||||
|
@using Microsoft.AspNetCore.Mvc.Localization
|
||||||
|
@inject IViewLocalizer T
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = T["Accessibility"];
|
||||||
|
}
|
||||||
|
|
||||||
|
<div typeof="WebPage" vocab="http://schema.org/">
|
||||||
|
<p property="accessibilitySummary">@T["PageIntro",
|
||||||
|
"28 November 2025",
|
||||||
|
new HtmlString("<a href=\"https://www.w3.org/TR/2024/REC-WCAG22-20241212/\">https://www.w3.org/TR/2024/REC-WCAG22-20241212/</a>")
|
||||||
|
])
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>@T["ReliesUpon"]
|
||||||
|
HTML 5.</li>
|
||||||
|
<li>@T["UsesButNotReliesUpon"] CSS2.1, gif.</li>
|
||||||
|
<li>@T["TestedWith"] @T["OnWith", "Firefox 145.0.2", "Kubuntu", "Orca"]
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>@T["Features"]</p>
|
||||||
|
</div>
|
||||||
@@ -30,17 +30,17 @@
|
|||||||
<table class="table table-striped align-middle">
|
<table class="table table-striped align-middle">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>@T["Owner"]</th>
|
<th id="col-owner">@T["Owner"]</th>
|
||||||
<th>@T["Asset ID"]</th>
|
<th id="col-assetId">@T["Asset ID"]</th>
|
||||||
<th>@T["Asset Name"]</th>
|
<th id="col-assetName">@T["Asset Name"]</th>
|
||||||
<th>@T["Location"]</th>
|
<th id="col-assetLocation">@T["Location"]</th>
|
||||||
<th class="text-center">@T["Action"]</th>
|
<th class="text-center">@T["Action"]</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th><input type="text" class="form-control form-control-sm column-filter" placeholder="@T["Owner"]" data-column="0" /></th>
|
<th><input aria-labelledby="col-owner" type="text" class="form-control form-control-sm column-filter" placeholder="@T["Owner"]" data-column="0" /></th>
|
||||||
<th><input type="text" class="form-control form-control-sm column-filter" placeholder="@T["Asset ID"]" data-column="1" /></th>
|
<th><input aria-labelledby="col-assetId" type="text" class="form-control form-control-sm column-filter" placeholder="@T["Asset ID"]" data-column="1" /></th>
|
||||||
<th><input type="text" class="form-control form-control-sm column-filter" placeholder="@T["Asset Name"]" data-column="2" /></th>
|
<th><input aria-labelledby="col-assetName" type="text" class="form-control form-control-sm column-filter" placeholder="@T["Asset Name"]" data-column="2" /></th>
|
||||||
<th><input type="text" class="form-control form-control-sm column-filter" placeholder="@T["Location"]" data-column="3" /></th>
|
<th><input aria-labelledby="col-assetLocation" type="text" class="form-control form-control-sm column-filter" placeholder="@T["Location"]" data-column="3" /></th>
|
||||||
<th class="text-center">-</th>
|
<th class="text-center">-</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
@{
|
@{
|
||||||
foreach (AssetsTableViewModel assetsTableViewModel in Model.AssetsTableViewModels)
|
foreach (AssetsTableViewModel assetsTableViewModel in Model.AssetsTableViewModels)
|
||||||
{
|
{
|
||||||
<tr class="asset-row" data-asset-id="@assetsTableViewModel.AssetCn">
|
<tr tabindex="0" role="button" class="asset-row" data-asset-id="@assetsTableViewModel.AssetCn">
|
||||||
<td>@assetsTableViewModel.UserUID</td>
|
<td>@assetsTableViewModel.UserUID</td>
|
||||||
<td>@assetsTableViewModel.AssetCn</td>
|
<td>@assetsTableViewModel.AssetCn</td>
|
||||||
<td>@assetsTableViewModel.AssetName</td>
|
<td>@assetsTableViewModel.AssetName</td>
|
||||||
@@ -228,9 +228,18 @@
|
|||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<h4 class="fw-bold mb-0">@T["Attributes"]</h4>
|
<h4 class="fw-bold mb-0">@T["Attributes"]</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="attributesContainer" class="d-flex flex-column gap-2">
|
<a href="#addAttributeBtn" class="visually-hidden-focusable">@T["Jump to add attribute button"]</a>
|
||||||
<!-- Dynamic attribute rows will appear here -->
|
<table class="w-100">
|
||||||
</div>
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="visually-hidden">@T["Attribute name"]</th>
|
||||||
|
<th class="visually-hidden">@T["Attribute value"]</th>
|
||||||
|
<th class="visually-hidden">@T["Delete"]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="attributesContainer">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
<button type="button" class="btn btn-sm btn-primary mt-3" id="addAttributeBtn">
|
<button type="button" class="btn btn-sm btn-primary mt-3" id="addAttributeBtn">
|
||||||
@T["Add Attribute"]
|
@T["Add Attribute"]
|
||||||
</button>
|
</button>
|
||||||
@@ -298,12 +307,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const addAttributeBtn = document.getElementById('addAttributeBtn');
|
const addAttributeBtn = document.getElementById('addAttributeBtn');
|
||||||
|
|
||||||
addAttributeBtn.addEventListener('click', () => {
|
addAttributeBtn.addEventListener('click', () => {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('tr');
|
||||||
row.className = 'd-flex gap-2 align-items-center attribute-row';
|
row.className = 'attribute-row';
|
||||||
|
row.classList.add("mb-3");
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<input type="text" class="form-control" placeholder="@T["Attribute name"]" data-attr-name />
|
<td class="col-md-5 p-1">
|
||||||
<input type="text" class="form-control" placeholder="@T["Attribute value"]" data-attr-value />
|
<input type="text" class="form-control" placeholder="@T["Attribute name"]" data-attr-name />
|
||||||
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
</td>
|
||||||
|
<td class="col-md-5 p-1">
|
||||||
|
<input type="text" class="form-control" placeholder="@T["Attribute value"]" data-attr-value />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2 p-1">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
attributesContainer.appendChild(row);
|
attributesContainer.appendChild(row);
|
||||||
});
|
});
|
||||||
@@ -364,7 +380,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (tableBody) {
|
if (tableBody) {
|
||||||
const newRow = document.createElement('tr');
|
const newRow = document.createElement('tr');
|
||||||
newRow.innerHTML = `
|
newRow.innerHTML = `
|
||||||
<td>${jsonData.Owner || ''}</td>
|
<td tabindex="0">${jsonData.Owner || ''}</td>
|
||||||
<td>${result.assetId || ''}</td>
|
<td>${result.assetId || ''}</td>
|
||||||
<td>${jsonData.Name || ''}</td>
|
<td>${jsonData.Name || ''}</td>
|
||||||
<td>${jsonData.Location || ''}</td>
|
<td>${jsonData.Location || ''}</td>
|
||||||
@@ -463,7 +479,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<h4 class="fw-bold mb-0">@T["Attributes"]</h4>
|
<h4 class="fw-bold mb-0">@T["Attributes"]</h4>
|
||||||
</div>
|
</div>
|
||||||
<div id="updateAttributesContainer" class="d-flex flex-column gap-2"></div>
|
<a href="#updateAddAttributeBtn" class="visually-hidden-focusable">@T["Jump to add attribute button"]</a>
|
||||||
|
<table class="w-100">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="visually-hidden">@T["Attribute name"]</th>
|
||||||
|
<th class="visually-hidden">@T["Attribute value"]</th>
|
||||||
|
<th class="visually-hidden">@T["Delete"]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="updateAttributesContainer">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
<button type="button" class="btn btn-sm btn-primary mt-3" id="updateAddAttributeBtn">
|
<button type="button" class="btn btn-sm btn-primary mt-3" id="updateAddAttributeBtn">
|
||||||
@T["Add Attribute"]
|
@T["Add Attribute"]
|
||||||
</button>
|
</button>
|
||||||
@@ -510,12 +537,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let assetId = null;
|
let assetId = null;
|
||||||
|
|
||||||
addAttrBtn.addEventListener('click', () => {
|
addAttrBtn.addEventListener('click', () => {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('tr');
|
||||||
row.className = 'd-flex gap-2 align-items-center attribute-row';
|
row.className = 'attribute-row mb-3';
|
||||||
|
let randomId = crypto.randomUUID();
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<input type="text" class="form-control" placeholder="@T["Attribute name"]" aria-label="@T["Attribute name"]" data-attr-name />
|
<td class="col-md-5 p-1">
|
||||||
<input type="text" class="form-control" placeholder="@T["Attribute value"]" aria-label="@T["Attribute value"]" data-attr-value />
|
<label for="updateName-${randomId}" class="visually-hidden">@T["Attribute name"]</label>
|
||||||
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
<input id="updateName-${randomId}" type="text" class="form-control" placeholder="@T["Attribute name"]" data-attr-name />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-5 p-1">
|
||||||
|
<label for="updateValue-${randomId}" class="visually-hidden">@T["Attribute value"]</label>
|
||||||
|
<input id="updateValue-${randomId}" type="text" class="form-control" placeholder="@T["Attribute value"]" data-attr-value />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2 p-1">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
updateAttributesContainer.appendChild(row);
|
updateAttributesContainer.appendChild(row);
|
||||||
});
|
});
|
||||||
@@ -559,12 +595,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Attributes
|
// Attributes
|
||||||
if (asset.Description.Attributes) {
|
if (asset.Description.Attributes) {
|
||||||
for (const [attrName, attrValue] of Object.entries(asset.Description.Attributes)) {
|
for (const [attrName, attrValue] of Object.entries(asset.Description.Attributes)) {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('tr');
|
||||||
row.className = 'd-flex gap-2 align-items-center attribute-row';
|
let randomId = crypto.randomUUID();
|
||||||
|
row.className = 'attribute-row mb-3';
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<input type="text" class="form-control" value="${attrName}" aria-label="@T["Attribute name"]" data-attr-name />
|
<td class="col-md-5 p-1">
|
||||||
<input type="text" class="form-control" value="${attrValue}" aria-label="@T["Attribute value"]" data-attr-value />
|
<label for="updateName-${randomId}" class="visually-hidden">@T["Attribute name"]</label>
|
||||||
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
<input id="updateName-${randomId}" type="text" class="form-control" value="${attrName}" placeholder="@T["Attribute name"]" data-attr-name />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-5 p-1">
|
||||||
|
<label for="updateValue-${randomId}" class="visually-hidden">@T["Attribute value"]</label>
|
||||||
|
<input id="updateValue-${randomId}" type="text" class="form-control" value="${attrValue}" placeholder="@T["Attribute value"]" data-attr-value />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2 p-1">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
updateAttributesContainer.appendChild(row);
|
updateAttributesContainer.appendChild(row);
|
||||||
}
|
}
|
||||||
@@ -672,7 +717,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
.asset-row > td {
|
.asset-row > td {
|
||||||
transition: 0.1s ease;
|
transition: 0.1s ease;
|
||||||
}
|
}
|
||||||
.asset-row:has(td:not(:last-child):hover) > td {
|
.asset-row:has(td:not(:last-child):is(:hover, :focus)) > td {
|
||||||
background-color: #17a2b8;
|
background-color: #17a2b8;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -686,145 +731,152 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function registerRowDetailviewClick(row) {
|
function registerRowDetailviewClick(row) {
|
||||||
const viewModal = document.getElementById('viewAssetModal');
|
row.addEventListener('click', async (e) => handleRowDetailViewEvent(e, row));
|
||||||
const viewContent = document.getElementById('viewAssetContent');
|
row.addEventListener('keydown', e => {
|
||||||
row.addEventListener('click', async (e) => {
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
// Avoid clicks on buttons inside the row
|
row.click();
|
||||||
if (e.target.closest('button')) return;
|
|
||||||
|
|
||||||
const assetId = row.getAttribute('data-asset-id');
|
|
||||||
viewContent.innerHTML = '<p class="text-center text-muted">@T["Loading..."]</p>';
|
|
||||||
|
|
||||||
const modal = new bootstrap.Modal(viewModal);
|
|
||||||
modal.show();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/Assets/Get?cn=${assetId}`);
|
|
||||||
const json = await response.json();
|
|
||||||
const asset = json.assetsModel;
|
|
||||||
|
|
||||||
if (!asset) {
|
|
||||||
viewContent.innerHTML = `<p class="text-danger text-center">@T["Asset not found."]</p>`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div class="row g-3">
|
|
||||||
<h4 class="fw-bold">@T["Barcode"]</h4>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<svg id="@barcodeType" class="form-control" name="Barcode" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<button id="downloadBtn" class="form-control my-2 btn btn-primary">@T["Download Barcode"]</button>
|
|
||||||
<button id="printBtn" class="form-control my-2 btn btn-primary">@T["Add Barcode to print batch"]</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="my-3" />
|
|
||||||
<div class="row g-3">
|
|
||||||
<h4 class="fw-bold">@T["Inventory"]</h4>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Name"]</label>
|
|
||||||
<input type="text" class="form-control" name="Name" value="${asset.Description.Inventory.PersonUid || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Date"]</label>
|
|
||||||
<input type="text" class="form-control" name="Name" value="${new Intl.DateTimeFormat('de-DE', {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}).format(new Date(asset.Description.Inventory.Date)) || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="my-3" />
|
|
||||||
<div class="row g-3">
|
|
||||||
<h4 class="fw-bold">@T["Information"]</h4>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Name"]</label>
|
|
||||||
<input type="text" class="form-control" name="Name" value="${asset.Name || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Location"]</label>
|
|
||||||
<input type="text" class="form-control" name="Location" value="${asset.Location || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Owner"]</label>
|
|
||||||
<input type="text" class="form-control" name="Owner" value="${asset.Owner || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Serial Number"]</label>
|
|
||||||
<input type="text" class="form-control" name="SerialNumber" value="${asset.SerialNumber || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr class="my-3" />
|
|
||||||
<div class="row g-3">
|
|
||||||
<h4 class="fw-bold">@T["Description"]</h4>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Type"]</label>
|
|
||||||
<input type="text" class="form-control" name="Description.Type" value="${asset.Description?.Type || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Make"]</label>
|
|
||||||
<input type="text" class="form-control" name="Description.Make" value="${asset.Description?.Make || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Model"]</label>
|
|
||||||
<input type="text" class="form-control" name="Description.Model" value="${asset.Description?.Model || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${asset.Description?.Attributes ? `
|
|
||||||
<hr class="my-3" />
|
|
||||||
<div class="row g-3">
|
|
||||||
<h4 class="fw-bold">@T["Attributes"]</h4>
|
|
||||||
${Object.entries(asset.Description.Attributes)
|
|
||||||
.map(([k,v]) => `
|
|
||||||
<div class="d-flex gap-2 align-items-center attribute-row">
|
|
||||||
<input type="text" class="form-control w-50" placeholder="@T["Attribute name"]" data-attr-name disabled value="${k}" />:
|
|
||||||
<input type="text" class="form-control" placeholder="@T["Attribute value"]" data-attr-value disabled value="${v}" />
|
|
||||||
</div>`)
|
|
||||||
.join('')}
|
|
||||||
</div>` : ''}
|
|
||||||
|
|
||||||
${asset.Description?.Purchase ? `
|
|
||||||
<hr class="my-3" />
|
|
||||||
<div class="row g-3">
|
|
||||||
<h4 class="fw-bold">@T["Purchase Information"]</h4>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Purchase Date"]</label>
|
|
||||||
<input type="date" class="form-control" name="Description.Purchase.PurchaseDate" value="${asset.Description.Purchase.PurchaseDate || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Purchase Value"]</label>
|
|
||||||
<input type="text" class="form-control" name="Description.Purchase.PurchaseValue" value="${asset.Description.Purchase.PurchaseValue || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Purchased At"]</label>
|
|
||||||
<input type="text" class="form-control" name="Description.Purchase.PurchaseAt" value="${asset.Description.Purchase.PurchaseAt || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label">@T["Purchased By"]</label>
|
|
||||||
<input type="text" class="form-control" name="Description.Purchase.PurchaseBy" value="${asset.Description.Purchase.PurchaseBy || ''}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>` : ''}
|
|
||||||
</div>`;
|
|
||||||
viewContent.innerHTML = html;
|
|
||||||
JsBarcode("#@barcodeType", getBarcodeValue("@barcodeType", asset.Cn), {
|
|
||||||
format: "@barcodeType",
|
|
||||||
lineColor: "#000",
|
|
||||||
width: 2,
|
|
||||||
height: 80,
|
|
||||||
displayValue: true
|
|
||||||
});
|
|
||||||
document.getElementById("downloadBtn").addEventListener("click", () => {
|
|
||||||
downloadBarcode("@barcodeType", getBarcodeValue("@barcodeType", asset.Cn));
|
|
||||||
});
|
|
||||||
document.getElementById("printBtn").addEventListener("click", () => {
|
|
||||||
addAssetIdToBatch(asset.Cn);
|
|
||||||
showToast("@T["Successfully added barcode to print batch"]", "success");
|
|
||||||
bootstrap.Modal.getInstance('#viewAssetModal').hide();
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
viewContent.innerHTML = `<p class="text-danger text-center">@T["Error loading asset details"]</p>`;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleRowDetailViewEvent(e, row) {
|
||||||
|
const viewModal = document.getElementById('viewAssetModal');
|
||||||
|
const viewContent = document.getElementById('viewAssetContent');
|
||||||
|
// Avoid clicks on buttons inside the row
|
||||||
|
if (e.target.closest('button')) return;
|
||||||
|
|
||||||
|
const assetId = row.getAttribute('data-asset-id');
|
||||||
|
viewContent.innerHTML = '<p class="text-center text-muted">@T["Loading..."]</p>';
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal(viewModal);
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/Assets/Get?cn=${assetId}`);
|
||||||
|
const json = await response.json();
|
||||||
|
const asset = json.assetsModel;
|
||||||
|
|
||||||
|
if (!asset) {
|
||||||
|
viewContent.innerHTML = `<p class="text-danger text-center">@T["Asset not found."]</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = 0;
|
||||||
|
const html = `
|
||||||
|
<div class="row g-3">
|
||||||
|
<h4 class="fw-bold">@T["Barcode"]</h4>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<svg role="img" aria-label="@T["Barcode"]" id="@barcodeType" class="form-control" name="Barcode" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<button id="downloadBtn" class="form-control my-2 btn btn-primary">@T["Download Barcode"]</button>
|
||||||
|
<button id="printBtn" class="form-control my-2 btn btn-primary">@T["Add Barcode to print batch"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-3" />
|
||||||
|
<div class="row g-3">
|
||||||
|
<h4 class="fw-bold">@T["Inventory"]</h4>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailUsername" class="form-label">@T["Name"]</label>
|
||||||
|
<input id="detailUsername" type="text" class="form-control" name="Name" value="${asset.Description.Inventory.PersonUid || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailDate" class="form-label">@T["Date"]</label>
|
||||||
|
<input id="detailDate" type="text" class="form-control" name="Date" value="${new Intl.DateTimeFormat('de-DE', {year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"}).format(new Date(asset.Description.Inventory.Date)) || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-3" />
|
||||||
|
<div class="row g-3">
|
||||||
|
<h4 class="fw-bold">@T["Information"]</h4>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailName" class="form-label">@T["Name"]</label>
|
||||||
|
<input id="detailName" type="text" class="form-control" name="Name" value="${asset.Name || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailLocation" class="form-label">@T["Location"]</label>
|
||||||
|
<input id="detailLocation" type="text" class="form-control" name="Location" value="${asset.Location || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailOwner" class="form-label">@T["Owner"]</label>
|
||||||
|
<input id="detailOwner" type="text" class="form-control" name="Owner" value="${asset.Owner || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailSerialnumber" class="form-label">@T["Serial Number"]</label>
|
||||||
|
<input id="detailSerialnumber" type="text" class="form-control" name="SerialNumber" value="${asset.SerialNumber || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-3" />
|
||||||
|
<div class="row g-3">
|
||||||
|
<h4 class="fw-bold">@T["Description"]</h4>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailType" class="form-label">@T["Type"]</label>
|
||||||
|
<input id="detailType" type="text" class="form-control" name="Description.Type" value="${asset.Description?.Type || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailMake" class="form-label">@T["Make"]</label>
|
||||||
|
<input id="detailMake" type="text" class="form-control" name="Description.Make" value="${asset.Description?.Make || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailModel" class="form-label">@T["Model"]</label>
|
||||||
|
<input id="detailModel" type="text" class="form-control" name="Description.Model" value="${asset.Description?.Model || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${asset.Description?.Attributes ? `
|
||||||
|
<hr class="my-3" />
|
||||||
|
<div class="row g-3">
|
||||||
|
<h4 class="fw-bold">@T["Attributes"]</h4>
|
||||||
|
${Object.entries(asset.Description.Attributes)
|
||||||
|
.map(([k,v]) => `
|
||||||
|
<div class="d-flex gap-2 align-items-center attribute-row">
|
||||||
|
<label for="detailAttributeName-${++i}" class="visually-hidden">${k}</label><input id="detailAttributeName-${i}" type="text" class="form-control w-50" placeholder="@T["Attribute name"]" data-attr-name disabled value="${k}" />:
|
||||||
|
<label for="detailAttributeValue-${i}" class="visually-hidden">${k}</label><input id="detailAttributeValue-${i}" type="text" class="form-control" placeholder="@T["Attribute value"]" data-attr-value disabled value="${v}" />
|
||||||
|
</div>`)
|
||||||
|
.join('')}
|
||||||
|
</div>` : ''}
|
||||||
|
|
||||||
|
${asset.Description?.Purchase ? `
|
||||||
|
<hr class="my-3" />
|
||||||
|
<div class="row g-3">
|
||||||
|
<h4 class="fw-bold">@T["Purchase Information"]</h4>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailPurchaseDate" class="form-label">@T["Purchase Date"]</label>
|
||||||
|
<input id="detailPurchaseDate" type="text" class="form-control" name="Description.Purchase.PurchaseDate" value="${new Intl.DateTimeFormat('de-DE', {year: "numeric", month: "2-digit", day: "2-digit"}).format(new Date(asset.Description.Purchase.PurchaseDate)) || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailPurchaseValue" class="form-label">@T["Purchase Value"]</label>
|
||||||
|
<input id="detailPurchaseValue" type="text" class="form-control" name="Description.Purchase.PurchaseValue" value="${asset.Description.Purchase.PurchaseValue || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailPurchaseAt" class="form-label">@T["Purchased At"]</label>
|
||||||
|
<input id="detailPurchaseAt" type="text" class="form-control" name="Description.Purchase.PurchaseAt" value="${asset.Description.Purchase.PurchaseAt || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="detailPurchaseBy" class="form-label">@T["Purchased By"]</label>
|
||||||
|
<input id="detailPurchaseBy" type="text" class="form-control" name="Description.Purchase.PurchaseBy" value="${asset.Description.Purchase.PurchaseBy || ''}" disabled />
|
||||||
|
</div>
|
||||||
|
</div>` : ''}
|
||||||
|
</div>`;
|
||||||
|
viewContent.innerHTML = html;
|
||||||
|
JsBarcode("#@barcodeType", getBarcodeValue("@barcodeType", asset.Cn), {
|
||||||
|
format: "@barcodeType",
|
||||||
|
lineColor: "#000",
|
||||||
|
width: 2,
|
||||||
|
height: 80,
|
||||||
|
displayValue: true
|
||||||
|
});
|
||||||
|
document.getElementById("downloadBtn").addEventListener("click", () => {
|
||||||
|
downloadBarcode("@barcodeType", getBarcodeValue("@barcodeType", asset.Cn));
|
||||||
|
});
|
||||||
|
document.getElementById("printBtn").addEventListener("click", () => {
|
||||||
|
addAssetIdToBatch(asset.Cn);
|
||||||
|
showToast("@T["Successfully added barcode to print batch"]", "success");
|
||||||
|
bootstrap.Modal.getInstance('#viewAssetModal').hide();
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
viewContent.innerHTML = `<p class="text-danger text-center">@T["Error loading asset details"]</p>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const presetApplyButton = document.getElementById('createPresetApply');
|
const presetApplyButton = document.getElementById('createPresetApply');
|
||||||
@@ -876,12 +928,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
if (anyEquals) {
|
if (anyEquals) {
|
||||||
anyEqualsElement.value = attributeValue;
|
anyEqualsElement.value = attributeValue;
|
||||||
} else {
|
} else {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('tr');
|
||||||
row.className = 'd-flex gap-2 align-items-center attribute-row';
|
row.className = 'attribute-row mb-3';
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<input type="text" class="form-control" placeholder="@T["Attribute name"]" data-attr-name value="${attributeKey}" />
|
<td class="col-md-5 p-1">
|
||||||
<input type="text" class="form-control" placeholder="@T["Attribute value"]" data-attr-value value="${attributeValue}" />
|
<input type="text" class="form-control" placeholder="@T["Attribute name"]" data-attr-name value="${attributeKey}" />
|
||||||
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
</td>
|
||||||
|
<td class="col-md-5 p-1">
|
||||||
|
<input type="text" class="form-control" placeholder="@T["Attribute value"]" data-attr-value value="${attributeValue}" />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2 p-1">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
attributesContainer.appendChild(row);
|
attributesContainer.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,12 +39,12 @@
|
|||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">@groupTableViewModel.Group</td>
|
<td class="text-center">@groupTableViewModel.Group</td>
|
||||||
<td class="text-center @(groupTableViewModel.CanInventorize ? "text-success" : "text-danger")">@(groupTableViewModel.CanInventorize ? "✓" : "✗")</td>
|
<td class="text-center @(groupTableViewModel.CanInventorize ? "text-success" : "text-danger")"><span class="visually-hidden">@T["inventorize"]</span>@(groupTableViewModel.CanInventorize ? Html.Raw($"<span aria-hidden=\"true\">✓</span><span class=\"visually-hidden\">{@T["Yes"].Value}</span>") : Html.Raw($"<span aria-hidden=\"true\">✗</span><span class=\"visually-hidden\">{@T["No"].Value}</span>"))</td>
|
||||||
<td class="text-center @(groupTableViewModel.CanManageUsers ? "text-success" : "text-danger")">@(groupTableViewModel.CanManageUsers ? "✓" : "✗")</td>
|
<td class="text-center @(groupTableViewModel.CanManageUsers ? "text-success" : "text-danger")"><span class="visually-hidden">@T["manage users"]</span>@(groupTableViewModel.CanManageUsers ? Html.Raw($"<span aria-hidden=\"true\">✓</span><span class=\"visually-hidden\">{@T["Yes"].Value}</span>") : Html.Raw($"<span aria-hidden=\"true\">✗</span><span class=\"visually-hidden\">{@T["No"].Value}</span>"))</td>
|
||||||
<td class="text-center @(groupTableViewModel.CanManageLocations ? "text-success" : "text-danger")">@(groupTableViewModel.CanManageLocations ? "✓" : "✗")</td>
|
<td class="text-center @(groupTableViewModel.CanManageLocations ? "text-success" : "text-danger")"><span class="visually-hidden">@T["manage locations"]</span>@(groupTableViewModel.CanManageLocations ? Html.Raw($"<span aria-hidden=\"true\">✓</span><span class=\"visually-hidden\">{@T["Yes"].Value}</span>") : Html.Raw($"<span aria-hidden=\"true\">✗</span><span class=\"visually-hidden\">{@T["No"].Value}</span>"))</td>
|
||||||
<td class="text-center @(groupTableViewModel.CanManageAssets ? "text-success" : "text-danger")">@(groupTableViewModel.CanManageAssets ? "✓" : "✗")</td>
|
<td class="text-center @(groupTableViewModel.CanManageAssets ? "text-success" : "text-danger")"><span class="visually-hidden">@T["manage assets"]</span>@(groupTableViewModel.CanManageAssets ? Html.Raw($"<span aria-hidden=\"true\">✓</span><span class=\"visually-hidden\">{@T["Yes"].Value}</span>") : Html.Raw($"<span aria-hidden=\"true\">✗</span><span class=\"visually-hidden\">{@T["No"].Value}</span>"))</td>
|
||||||
<td class="text-center @(groupTableViewModel.CanManageGroups ? "text-success" : "text-danger")">@(groupTableViewModel.CanManageGroups ? "✓" : "✗")</td>
|
<td class="text-center @(groupTableViewModel.CanManageGroups ? "text-success" : "text-danger")"><span class="visually-hidden">@T["manage groups"]</span>@(groupTableViewModel.CanManageGroups ? Html.Raw($"<span aria-hidden=\"true\">✓</span><span class=\"visually-hidden\">{@T["Yes"].Value}</span>") : Html.Raw($"<span aria-hidden=\"true\">✗</span><span class=\"visually-hidden\">{@T["No"].Value}</span>"))</td>
|
||||||
<td class="text-center @(groupTableViewModel.CanManageSettings ? "text-success" : "text-danger")">@(groupTableViewModel.CanManageSettings ? "✓" : "✗")</td>
|
<td class="text-center @(groupTableViewModel.CanManageSettings ? "text-success" : "text-danger")"><span class="visually-hidden">@T["manage settings"]</span>@(groupTableViewModel.CanManageSettings ? Html.Raw($"<span aria-hidden=\"true\">✓</span><span class=\"visually-hidden\">{@T["Yes"].Value}</span>") : Html.Raw($"<span aria-hidden=\"true\">✗</span><span class=\"visually-hidden\">{@T["No"].Value}</span>"))</td>
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="d-flex gap-2 justify-content-center">
|
<div class="d-flex gap-2 justify-content-center">
|
||||||
<button class="btn btn-sm btn-warning btn-update"
|
<button class="btn btn-sm btn-warning btn-update"
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>@T["GroupDeleteConfirmation1"] <strong id="groupName"></strong> (ID: <span id="groupId"></span>)@T["GroupDeleteConfirmation2"]</p>
|
<p>@T["GroupDeleteConfirmation1"] <strong id="deleteGroupName"></strong> (ID: <span id="deleteGroupId"></span>)@T["GroupDeleteConfirmation2"]</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">@T["Cancel"]</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">@T["Cancel"]</button>
|
||||||
@@ -101,8 +101,8 @@
|
|||||||
const groupId = currentButton.getAttribute('data-group-id');
|
const groupId = currentButton.getAttribute('data-group-id');
|
||||||
const groupName = currentButton.getAttribute('data-group-name');
|
const groupName = currentButton.getAttribute('data-group-name');
|
||||||
|
|
||||||
deleteModal.querySelector('#groupId').textContent = groupId;
|
deleteModal.querySelector('#deleteGroupId').textContent = groupId;
|
||||||
deleteModal.querySelector('#groupName').textContent = groupName;
|
deleteModal.querySelector('#deleteGroupName').textContent = groupName;
|
||||||
|
|
||||||
// Store the delete URL for later use
|
// Store the delete URL for later use
|
||||||
deleteModal.querySelector('#deleteForm').dataset.url = `/Groups/Delete?uid=${groupId}`;
|
deleteModal.querySelector('#deleteForm').dataset.url = `/Groups/Delete?uid=${groupId}`;
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const url = deleteForm.dataset.url;
|
const url = deleteForm.dataset.url;
|
||||||
const groupId = deleteModal.querySelector('#groupId').textContent;
|
const groupId = deleteModal.querySelector('#deleteGroupId').textContent;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
@@ -171,16 +171,16 @@
|
|||||||
<div class="row g-3 justify-content-center">
|
<div class="row g-3 justify-content-center">
|
||||||
<!-- Basic Info -->
|
<!-- Basic Info -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="cn">@T["Group ID"] *</label>
|
<label class="form-label" for="cn">@T["Group ID"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" id="cn" class="form-control" name="Cn" required />
|
<input type="text" id="cn" class="form-control" name="Cn" aria-required="true" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="displayname">@T["Display Name"] *</label>
|
<label class="form-label" for="displayname">@T["Display Name"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" id="displayname" class="form-control" name="DisplayName" />
|
<input type="text" id="displayname" class="form-control" name="DisplayName" aria-required="true" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check mb-3">
|
||||||
<input type="checkbox" class="form-check-input" name="Permissions.CanInventorize" id="canInventorize" />
|
<input type="checkbox" class="form-check-input" name="Permissions.CanInventorize" id="canInventorize" />
|
||||||
<label class="form-check-label" for="canInventorize">@T["Can inventorize"]</label>
|
<label class="form-check-label" for="canInventorize">@T["Can inventorize"]</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check mb-3">
|
||||||
<input type="checkbox" class="form-check-input" name="Permissions.CanManageUsers" id="canManageUsers" />
|
<input type="checkbox" class="form-check-input" name="Permissions.CanManageUsers" id="canManageUsers" />
|
||||||
<label class="form-check-label" for="canManageUsers">@T["Can manage users"]</label>
|
<label class="form-check-label" for="canManageUsers">@T["Can manage users"]</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check mb-3">
|
||||||
<input type="checkbox" class="form-check-input" name="Permissions.CanManageLocations" id="canManageLocations" />
|
<input type="checkbox" class="form-check-input" name="Permissions.CanManageLocations" id="canManageLocations" />
|
||||||
<label class="form-check-label" for="canManageLocations">@T["Can manage locations"]</label>
|
<label class="form-check-label" for="canManageLocations">@T["Can manage locations"]</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,6 +223,9 @@
|
|||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
const contentYes = `<span aria-hidden=\"true\">✓</span><span class=\"visually-hidden\">@T["Yes"].Value</span>`;
|
||||||
|
const contentNo = `<span aria-hidden=\"true\">✗</span><span class=\"visually-hidden\">@T["No"].Value</span>`;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const createForm = document.getElementById('createGroupForm');
|
const createForm = document.getElementById('createGroupForm');
|
||||||
|
|
||||||
@@ -264,16 +267,15 @@
|
|||||||
// Add the new group to the table
|
// Add the new group to the table
|
||||||
const tableBody = document.querySelector('tbody');
|
const tableBody = document.querySelector('tbody');
|
||||||
const newRow = document.createElement('tr');
|
const newRow = document.createElement('tr');
|
||||||
|
|
||||||
newRow.innerHTML = `
|
newRow.innerHTML = `
|
||||||
<td style="text-align: center">${jsonData.DisplayName}</td>
|
<td class="text-center">${jsonData.DisplayName}</td>
|
||||||
<td class="text-center ${jsonData.Permissions.includes("CanInventorize") ? "text-success" : "text-danger"}">${jsonData.Permissions.includes("CanInventorize") ? "✓" : "✗"}</td>
|
<td class="text-center ${jsonData.Permissions.includes("CanInventorize") ? "text-success" : "text-danger"}"><span class="visually-hidden">@T["inventorize"]</span>${jsonData.Permissions.includes("CanInventorize") ? contentYes : contentNo}</td>
|
||||||
<td class="text-center ${jsonData.Permissions.includes("CanManageUsers") ? "text-success" : "text-danger"}">${jsonData.Permissions.includes("CanManageUsers") ? "✓" : "✗"}</td>
|
<td class="text-center ${jsonData.Permissions.includes("CanManageUsers") ? "text-success" : "text-danger"}"><span class="visually-hidden">@T["manage users"]</span>${jsonData.Permissions.includes("CanManageUsers") ? contentYes : contentNo}</td>
|
||||||
<td class="text-center ${jsonData.Permissions.includes("CanManageLocations") ? "text-success" : "text-danger"}">${jsonData.Permissions.includes("CanManageLocations") ? "✓" : "✗"}</td>
|
<td class="text-center ${jsonData.Permissions.includes("CanManageLocations") ? "text-success" : "text-danger"}"><span class="visually-hidden">@T["manage locations"]</span>${jsonData.Permissions.includes("CanManageLocations") ? contentYes : contentNo}</td>
|
||||||
<td class="text-center ${jsonData.Permissions.includes("CanManageAssets") ? "text-success" : "text-danger"}">${jsonData.Permissions.includes("CanManageAssets") ? "✓" : "✗"}</td>
|
<td class="text-center ${jsonData.Permissions.includes("CanManageAssets") ? "text-success" : "text-danger"}"><span class="visually-hidden">@T["manage assets"]</span>${jsonData.Permissions.includes("CanManageAssets") ? contentYes : contentNo}</td>
|
||||||
<td class="text-center ${jsonData.Permissions.includes("CanManageGroups") ? "text-success" : "text-danger"}">${jsonData.Permissions.includes("CanManageGroups") ? "✓" : "✗"}</td>
|
<td class="text-center ${jsonData.Permissions.includes("CanManageGroups") ? "text-success" : "text-danger"}"><span class="visually-hidden">@T["manage groups"]</span>${jsonData.Permissions.includes("CanManageGroups") ? contentYes : contentNo}</td>
|
||||||
<td class="text-center ${jsonData.Permissions.includes("CanManageSettings") ? "text-success" : "text-danger"}">${jsonData.Permissions.includes("CanManageSettings") ? "✓" : "✗"}</td>
|
<td class="text-center ${jsonData.Permissions.includes("CanManageSettings") ? "text-success" : "text-danger"}"><span class="visually-hidden">@T["manage settings"]</span>${jsonData.Permissions.includes("CanManageSettings") ? contentYes : contentNo}</td>
|
||||||
<td style="text-align: center">
|
<td class="text-center">
|
||||||
<div class="d-flex gap-2 justify-content-center">
|
<div class="d-flex gap-2 justify-content-center">
|
||||||
<button class="btn btn-sm btn-warning btn-update"
|
<button class="btn btn-sm btn-warning btn-update"
|
||||||
data-group-id="${jsonData.Cn}"
|
data-group-id="${jsonData.Cn}"
|
||||||
@@ -325,45 +327,45 @@
|
|||||||
|
|
||||||
<form id="updateGroupForm">
|
<form id="updateGroupForm">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row g-3 justify-content-center">
|
<div class="row g-3 mb-2 justify-content-center">
|
||||||
<!-- Basic Info -->
|
<!-- Basic Info -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="groupId">@T["Group ID"] *</label>
|
<label class="form-label" for="groupId">@T["Group ID"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" id="groupId" class="form-control" name="Cn" required />
|
<input type="text" id="groupId" class="form-control" name="Cn" aria-required="true" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="dn">@T["Display Name"] *</label>
|
<label class="form-label" for="dn">@T["Display Name"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" id="dn" class="form-control" name="DisplayName" />
|
<input type="text" id="dn" class="form-control" name="DisplayName" aria-required="true" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check mb-3">
|
||||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanInventorize" id="canInventorize" />
|
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanInventorize" id="updateCanInventorize" />
|
||||||
<label class="form-check-label" for="canInventorize">@T["Can inventorize"]</label>
|
<label class="form-check-label" for="updateCanInventorize">@T["Can inventorize"]</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageAssets" id="canManageAssets" />
|
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageAssets" id="updateCanManageAssets" />
|
||||||
<label class="form-check-label" for="canManageAssets">@T["Can manage assets"]</label>
|
<label class="form-check-label" for="updateCanManageAssets">@T["Can manage assets"]</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check mb-3">
|
||||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageUsers" id="canManageUsers" />
|
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageUsers" id="updateCanManageUsers" />
|
||||||
<label class="form-check-label" for="canManageUsers">@T["Can manage users"]</label>
|
<label class="form-check-label" for="updateCanManageUsers">@T["Can manage users"]</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageGroups" id="canManageGroups" />
|
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageGroups" id="updateCanManageGroups" />
|
||||||
<label class="form-check-label" for="canManageGroups">@T["Can manage groups"]</label>
|
<label class="form-check-label" for="updateCanManageGroups">@T["Can manage groups"]</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="form-check">
|
<div class="form-check mb-3">
|
||||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageLocations" id="canManageLocations" />
|
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageLocations" id="updateCanManageLocations" />
|
||||||
<label class="form-check-label" for="canManageLocations">@T["Can manage locations"]</label>
|
<label class="form-check-label" for="updateCanManageLocations">@T["Can manage locations"]</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageSettings" id="canManageSettings" />
|
<input type="checkbox" class="form-check-input" name="Description.Permissions.CanManageSettings" id="updateCanManageSettings" />
|
||||||
<label class="form-check-label" for="canManageSettings">@T["Can manage settings"]</label>
|
<label class="form-check-label" for="updateCanManageSettings">@T["Can manage settings"]</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -462,12 +464,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
.find(r => r.querySelector(`[data-group-id="${jsonData.Cn}"]`));
|
.find(r => r.querySelector(`[data-group-id="${jsonData.Cn}"]`));
|
||||||
if (row) {
|
if (row) {
|
||||||
row.children[0].textContent = jsonData.Description.DisplayName || '';
|
row.children[0].textContent = jsonData.Description.DisplayName || '';
|
||||||
row.children[1].textContent = jsonData.Description.Permissions.includes("CanInventorize") ? "✓" : "✗" || '';
|
row.children[1].innerHTML = `<span class="visually-hidden">@T["inventorize"]</span>` + (jsonData.Description.Permissions.includes("CanInventorize") ? contentYes : contentNo || '');
|
||||||
row.children[2].textContent = jsonData.Description.Permissions.includes("CanManageUsers") ? "✓" : "✗" || '';
|
row.children[2].innerHTML = `<span class="visually-hidden">@T["manage users"]</span>` + (jsonData.Description.Permissions.includes("CanManageUsers") ? contentYes : contentNo || '');
|
||||||
row.children[3].textContent = jsonData.Description.Permissions.includes("CanManageLocations") ? "✓" : "✗" || '';
|
row.children[3].innerHTML = `<span class="visually-hidden">@T["manage locations"]</span>` + (jsonData.Description.Permissions.includes("CanManageLocations") ? contentYes : contentNo || '');
|
||||||
row.children[4].textContent = jsonData.Description.Permissions.includes("CanManageAssets") ? "✓" : "✗" || '';
|
row.children[4].innerHTML = `<span class="visually-hidden">@T["manage assets"]</span>` + (jsonData.Description.Permissions.includes("CanManageAssets") ? contentYes : contentNo || '');
|
||||||
row.children[5].textContent = jsonData.Description.Permissions.includes("CanManageGroups") ? "✓" : "✗" || '';
|
row.children[5].innerHTML = `<span class="visually-hidden">@T["manage groups"]</span>` + (jsonData.Description.Permissions.includes("CanManageGroups") ? contentYes : contentNo || '');
|
||||||
row.children[6].textContent = jsonData.Description.Permissions.includes("CanManageSettings") ? "✓" : "✗" || '';
|
row.children[6].innerHTML = `<span class="visually-hidden">@T["manage settings"]</span>` + (jsonData.Description.Permissions.includes("CanManageSettings") ? contentYes : contentNo || '');
|
||||||
if (jsonData.Description.Permissions.includes("CanInventorize")) {
|
if (jsonData.Description.Permissions.includes("CanInventorize")) {
|
||||||
row.children[1].className = "text-center text-success";
|
row.children[1].className = "text-center text-success";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -52,4 +52,13 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="mb-4 d-flex flex-wrap gap-2 justify-content-center">
|
||||||
|
<a asp-controller="Settings" asp-action="User">@T["User settings"]</a>
|
||||||
|
@if (User.HasClaim(ClaimTypes.Role, "CanManageSettings"))
|
||||||
|
{
|
||||||
|
<a asp-controller="Settings" asp-action="Admin">@T["Admin settings"]</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-3 text-center">
|
<div class="col-md-3 text-center">
|
||||||
|
<label for="barcodeInput" class="visually-hidden">@T["Asset ID"]</label>
|
||||||
<input type="text" id="barcodeInput" class="form-control mt-3" placeholder="@T["Asset ID"]" />
|
<input type="text" id="barcodeInput" class="form-control mt-3" placeholder="@T["Asset ID"]" />
|
||||||
<button id="enterAssetIdManuallyButton" class="btn btn-secondary mt-3">@T["Enter asset ID manually"]</button>
|
<button id="enterAssetIdManuallyButton" class="btn btn-secondary mt-3">@T["Enter asset ID manually"]</button>
|
||||||
<div id="reader" style="display:none" class="mt-3"></div>
|
<div id="reader" style="display:none" class="mt-3"></div>
|
||||||
@@ -72,34 +73,37 @@
|
|||||||
|
|
||||||
<script defer>
|
<script defer>
|
||||||
|
|
||||||
async function onScanSuccess(decodedText, decodedResult) {
|
async function onScanSuccess(decodedText, decodedResult, isEnteredManually) {
|
||||||
const rawDecoded = decodedText;
|
const rawDecoded = decodedText;
|
||||||
const BARCODE_TYPE = "@barcodeType";
|
const BARCODE_TYPE = "@barcodeType";
|
||||||
switch (BARCODE_TYPE.toUpperCase()) {
|
if (!isEnteredManually)
|
||||||
case "EAN13":
|
{
|
||||||
decodedText = decodedText.slice(0,-1);
|
switch (BARCODE_TYPE.toUpperCase()) {
|
||||||
break;
|
case "EAN13":
|
||||||
case "EAN8":
|
decodedText = decodedText.slice(0,-1);
|
||||||
decodedText = decodedText.slice(0,-1);
|
break;
|
||||||
break;
|
case "EAN8":
|
||||||
case "UPC":
|
decodedText = decodedText.slice(0,-1);
|
||||||
decodedText = decodedText.slice(0,-1);
|
break;
|
||||||
break;
|
case "UPC":
|
||||||
case "ITF14":
|
decodedText = decodedText.slice(0,-1);
|
||||||
decodedText = decodedText.slice(0,-1);
|
break;
|
||||||
break;
|
case "ITF14":
|
||||||
case "MSI10":
|
decodedText = decodedText.slice(0,-1);
|
||||||
decodedText = decodedText.slice(0,-1);
|
break;
|
||||||
break;
|
case "MSI10":
|
||||||
case "MSI11":
|
decodedText = decodedText.slice(0,-1);
|
||||||
decodedText = decodedText.slice(0,-1);
|
break;
|
||||||
break;
|
case "MSI11":
|
||||||
case "MSI1010":
|
decodedText = decodedText.slice(0,-1);
|
||||||
decodedText = decodedText.slice(0,-2);
|
break;
|
||||||
break;
|
case "MSI1010":
|
||||||
case "MSI1110":
|
decodedText = decodedText.slice(0,-2);
|
||||||
decodedText = decodedText.slice(0,-1);
|
break;
|
||||||
break;
|
case "MSI1110":
|
||||||
|
decodedText = decodedText.slice(0,-1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
decodedText = decodedText.replace(/^0+/, '');
|
decodedText = decodedText.replace(/^0+/, '');
|
||||||
console.log(`Code matched = ${decodedText}`, decodedResult);
|
console.log(`Code matched = ${decodedText}`, decodedResult);
|
||||||
@@ -144,19 +148,19 @@
|
|||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailName">@T["Name"]</label>
|
<label class="form-label" for="detailName">@T["Name"]</label>
|
||||||
<input type="text" class="form-control" id="detailName" name="Name" value="${asset.Name || ''}" disabled />
|
<input type="text" class="form-control" id="detailName" name="Name" value="${asset.Name || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailLocation">@T["Location"]</label>
|
<label class="form-label" for="detailLocation">@T["Location"]</label>
|
||||||
<input type="text" class="form-control" id="detailLocation" name="Location" value="${asset.Location || ''}" disabled />
|
<input type="text" class="form-control" id="detailLocation" name="Location" value="${asset.Location || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailOwner">@T["Owner"]</label>
|
<label class="form-label" for="detailOwner">@T["Owner"]</label>
|
||||||
<input type="text" class="form-control" id="detailOwner" name="Owner" value="${asset.Owner || ''}" disabled />
|
<input type="text" class="form-control" id="detailOwner" name="Owner" value="${asset.Owner || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailSerialNumber">@T["Serial Number"]</label>
|
<label class="form-label" for="detailSerialNumber">@T["Serial Number"]</label>
|
||||||
<input type="text" class="form-control" id="detailSerialNumber" name="SerialNumber" value="${asset.SerialNumber || ''}" disabled />
|
<input type="text" class="form-control" id="detailSerialNumber" name="SerialNumber" value="${asset.SerialNumber || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-3" />
|
<hr class="my-3" />
|
||||||
@@ -164,28 +168,44 @@
|
|||||||
<h4 class="fw-bold">@T["Description"]</h4>
|
<h4 class="fw-bold">@T["Description"]</h4>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailType">@T["Type"]</label>
|
<label class="form-label" for="detailType">@T["Type"]</label>
|
||||||
<input type="text" class="form-control" id="detailType" name="Description.Type" value="${asset.Description?.Type || ''}" disabled />
|
<input type="text" class="form-control" id="detailType" name="Description.Type" value="${asset.Description?.Type || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailMake">@T["Make"]</label>
|
<label class="form-label" for="detailMake">@T["Make"]</label>
|
||||||
<input type="text" class="form-control" id="detailMake" name="Description.Make" value="${asset.Description?.Make || ''}" disabled />
|
<input type="text" class="form-control" id="detailMake" name="Description.Make" value="${asset.Description?.Make || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailModel">@T["Model"]</label>
|
<label class="form-label" for="detailModel">@T["Model"]</label>
|
||||||
<input type="text" class="form-control" id="detailModel" name="Description.Model" value="${asset.Description?.Model || ''}" disabled />
|
<input type="text" class="form-control" id="detailModel" name="Description.Model" value="${asset.Description?.Model || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${asset.Description?.Attributes ? `
|
${asset.Description?.Attributes ? `
|
||||||
<hr class="my-3" />
|
<hr class="my-3" />
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<h4 class="fw-bold">@T["Attributes"]</h4>
|
<h4 class="fw-bold">@T["Attributes"]</h4>
|
||||||
${Object.entries(asset.Description.Attributes)
|
<table class="w-100">
|
||||||
.map(([k,v]) => `
|
<thead>
|
||||||
<div class="d-flex gap-2 align-items-center attribute-row">
|
<tr>
|
||||||
<input type="text" class="form-control w-50" placeholder="@T["Attribute name"]" aria-label="@T["Attribute name"]" data-attr-name disabled value="${k}" />:
|
<th class="visually-hidden">@T["Attribute name"]</th>
|
||||||
<input type="text" class="form-control" placeholder="@T["Attribute value"]" aria-label="@T["Attribute value"]" data-attr-value disabled value="${v}" />
|
<th class="visually-hidden">@T["Attribute value"]</th>
|
||||||
</div>`)
|
<th class="visually-hidden">@T["Delete"]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="updateAttributesContainer">
|
||||||
|
${Object.entries(asset.Description.Attributes)
|
||||||
|
.map(([k,v]) => `
|
||||||
|
<tr class="gap-2 align-items-center attribute-row">
|
||||||
|
<td class="d-flex p-1 align-items-center">
|
||||||
|
<input type="text" class="form-control d-inline-block w-100" placeholder="@T["Attribute name"]" aria-label="@T["Attribute name"]" data-attr-name readonly value="${k}" />
|
||||||
|
<span class="ms-1">:</span>
|
||||||
|
</td>
|
||||||
|
<td class="p-1">
|
||||||
|
<input type="text" class="form-control" placeholder="@T["Attribute value"]" aria-label="@T["Attribute value"]" data-attr-value readonly value="${v}" />
|
||||||
|
</td>
|
||||||
|
</tr>`)
|
||||||
.join('')}
|
.join('')}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>` : ''}
|
</div>` : ''}
|
||||||
|
|
||||||
${asset.Description?.Purchase ? `
|
${asset.Description?.Purchase ? `
|
||||||
@@ -194,19 +214,19 @@
|
|||||||
<h4 class="fw-bold">@T["Purchase Information"]</h4>
|
<h4 class="fw-bold">@T["Purchase Information"]</h4>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailPurchaseDate">@T["Purchase Date"]</label>
|
<label class="form-label" for="detailPurchaseDate">@T["Purchase Date"]</label>
|
||||||
<input type="date" class="form-control" id="detailPurchaseDate" name="Description.Purchase.PurchaseDate" value="${asset.Description.Purchase.PurchaseDate || ''}" disabled />
|
<input type="text" class="form-control" id="detailPurchaseDate" name="Description.Purchase.PurchaseDate" value="${asset.Description.Purchase.PurchaseDate || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailPurchaseValue">@T["Purchase Value"]</label>
|
<label class="form-label" for="detailPurchaseValue">@T["Purchase Value"]</label>
|
||||||
<input type="text" class="form-control" id="detailPurchaseValue" name="Description.Purchase.PurchaseValue" value="${asset.Description.Purchase.PurchaseValue || ''}" disabled />
|
<input type="text" class="form-control" id="detailPurchaseValue" name="Description.Purchase.PurchaseValue" value="${asset.Description.Purchase.PurchaseValue || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailPurchaseAt">@T["Purchased At"]</label>
|
<label class="form-label" for="detailPurchaseAt">@T["Purchased At"]</label>
|
||||||
<input type="text" class="form-control" id="detailPurchaseAt" name="Description.Purchase.PurchaseAt" value="${asset.Description.Purchase.PurchaseAt || ''}" disabled />
|
<input type="text" class="form-control" id="detailPurchaseAt" name="Description.Purchase.PurchaseAt" value="${asset.Description.Purchase.PurchaseAt || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="detailPurchaseBy">@T["Purchased By"]</label>
|
<label class="form-label" for="detailPurchaseBy">@T["Purchased By"]</label>
|
||||||
<input type="text" class="form-control" id="detailPurchaseBy" name="Description.Purchase.PurchaseBy" value="${asset.Description.Purchase.PurchaseBy || ''}" disabled />
|
<input type="text" class="form-control" id="detailPurchaseBy" name="Description.Purchase.PurchaseBy" value="${asset.Description.Purchase.PurchaseBy || ''}" readonly />
|
||||||
</div>
|
</div>
|
||||||
</div>` : ''}
|
</div>` : ''}
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -251,7 +271,7 @@
|
|||||||
console.warn("Could not stop scanner:", err);
|
console.warn("Could not stop scanner:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await onScanSuccess(document.getElementById("barcodeInput").value, null);
|
await onScanSuccess(document.getElementById("barcodeInput").value, null, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const scanBarcodeButton = document.querySelector('#scanBarcodeButton');
|
const scanBarcodeButton = document.querySelector('#scanBarcodeButton');
|
||||||
@@ -385,10 +405,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 mt-3">
|
<div class="col-12 mt-3">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="justify-content-between align-items-center mb-2">
|
||||||
<h4 class="fw-bold mb-0">@T["Attributes"]</h4>
|
<h4 class="fw-bold mb-0">@T["Attributes"]</h4>
|
||||||
|
<a href="#updateAddAttributeBtn" class="visually-hidden-focusable">@T["Jump to add attribute button"]</a>
|
||||||
|
<table class="w-100">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="visually-hidden">@T["Attribute name"]</th>
|
||||||
|
<th class="visually-hidden">@T["Attribute value"]</th>
|
||||||
|
<th class="visually-hidden">@T["Delete"]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="updateAttributesContainer">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div id="updateAttributesContainer" class="d-flex flex-column gap-2"></div>
|
|
||||||
<button type="button" class="btn btn-sm btn-primary mt-3" id="updateAddAttributeBtn">
|
<button type="button" class="btn btn-sm btn-primary mt-3" id="updateAddAttributeBtn">
|
||||||
@T["Add Attribute"]
|
@T["Add Attribute"]
|
||||||
</button>
|
</button>
|
||||||
@@ -435,12 +466,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let assetId = null;
|
let assetId = null;
|
||||||
|
|
||||||
addAttrBtn.addEventListener('click', () => {
|
addAttrBtn.addEventListener('click', () => {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('tr');
|
||||||
row.className = 'd-flex gap-2 align-items-center attribute-row';
|
row.className = 'attribute-row mb-3';
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<input type="text" class="form-control" aria-label="@T["Attribute name"]" placeholder="@T["Attribute name"]" data-attr-name />
|
<td class="col-md-5 p-1">
|
||||||
<input type="text" class="form-control" aria-label="@T["Attribute value"]" placeholder="@T["Attribute value"]" data-attr-value />
|
<input type="text" class="form-control" placeholder="@T["Attribute name"]" data-attr-name />
|
||||||
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
</td>
|
||||||
|
<td class="col-md-5 p-1">
|
||||||
|
<input type="text" class="form-control" placeholder="@T["Attribute value"]" data-attr-value />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2 p-1">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
updateAttributesContainer.appendChild(row);
|
updateAttributesContainer.appendChild(row);
|
||||||
});
|
});
|
||||||
@@ -486,12 +523,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// Attributes
|
// Attributes
|
||||||
if (asset.Description.Attributes) {
|
if (asset.Description.Attributes) {
|
||||||
for (const [attrName, attrValue] of Object.entries(asset.Description.Attributes)) {
|
for (const [attrName, attrValue] of Object.entries(asset.Description.Attributes)) {
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('tr');
|
||||||
row.className = 'd-flex gap-2 align-items-center attribute-row';
|
row.className = 'attribute-row mb-3';
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<input type="text" class="form-control" aria-label="@T["Attribute name"]" value="${attrName}" data-attr-name />
|
<td class="col-md-5 p-1">
|
||||||
<input type="text" class="form-control" aria-label="@T["Attribute value"]" value="${attrValue}" data-attr-value />
|
<input type="text" class="form-control" value="${attrName}" placeholder="@T["Attribute name"]" data-attr-name />
|
||||||
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
</td>
|
||||||
|
<td class="col-md-5 p-1">
|
||||||
|
<input type="text" class="form-control" value="${attrValue}" placeholder="@T["Attribute value"]" data-attr-value />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-2 p-1">
|
||||||
|
<button type="button" class="btn btn-danger btn-sm btn-remove-attribute">@T["Remove"]</button>
|
||||||
|
</td>
|
||||||
`;
|
`;
|
||||||
updateAttributesContainer.appendChild(row);
|
updateAttributesContainer.appendChild(row);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,8 +165,8 @@
|
|||||||
<input type="hidden" id="editLocationId" name="LocationID">
|
<input type="hidden" id="editLocationId" name="LocationID">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="editLocationName" class="form-label">@T["Location Name"]</label>
|
<label for="editLocationName" class="form-label">@T["Location Name"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" class="form-control" id="editLocationName" name="LocationName" required>
|
<input type="text" class="form-control" id="editLocationName" name="LocationName" aria-required="true" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
@@ -234,6 +234,7 @@
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result) {
|
if (result) {
|
||||||
const btn = document.querySelector(`button[data-location-id="${data.Location}"]`);
|
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');
|
const row = btn.closest('tr');
|
||||||
let slugifiedLocationID = `${data.Description.Location}-${data.Description.RoomNumber}-${data.Description.Seat}`
|
let slugifiedLocationID = `${data.Description.Location}-${data.Description.RoomNumber}-${data.Description.Seat}`
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
@@ -244,6 +245,10 @@
|
|||||||
btn.setAttribute("data-location-name", data.Description.Location);
|
btn.setAttribute("data-location-name", data.Description.Location);
|
||||||
btn.setAttribute("data-room-number", data.Description.RoomNumber);
|
btn.setAttribute("data-room-number", data.Description.RoomNumber);
|
||||||
btn.setAttribute("data-seat", data.Description.Seat);
|
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[0].textContent = slugifiedLocationID;
|
||||||
row.children[1].textContent = data.Description.Location;
|
row.children[1].textContent = data.Description.Location;
|
||||||
row.children[2].textContent = data.Description.RoomNumber;
|
row.children[2].textContent = data.Description.RoomNumber;
|
||||||
@@ -276,8 +281,8 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="createLocationName" class="form-label">@T["Location Name"]</label>
|
<label for="createLocationName" class="form-label">@T["Location Name"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" class="form-control" id="createLocationName" name="LocationName" required>
|
<input type="text" class="form-control" id="createLocationName" name="LocationName" aria-required="true" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
|
|||||||
@@ -15,17 +15,5 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<form method="post" action="/Home/Login" class="mt-4" style="max-width: 400px; margin: auto;">
|
<partial name="_Login" />
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label for="username" class="form-label">@T["Username"]</label>
|
|
||||||
<input autofocus type="text" class="form-control" id="username" name="username" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label for="password" class="form-label">@T["Password"]</label>
|
|
||||||
<input type="password" class="form-control" id="password" name="password" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary w-100">@T["Login"]</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -176,12 +176,12 @@
|
|||||||
<input type="text" id="updateTitle" name="Title" class="form-control" />
|
<input type="text" id="updateTitle" name="Title" class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="updateName">@T["Name"]</label>
|
<label class="form-label" for="updateName">@T["Name"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" id="updateName" name="Cn" class="form-control" />
|
<input type="text" id="updateName" name="Cn" aria-required="true" required class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="updateSurname">@T["Surname"]</label>
|
<label class="form-label" for="updateSurname">@T["Surname"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" id="updateSurname" name="Sn" class="form-control" />
|
<input type="text" id="updateSurname" name="Sn" aria-required="true" required class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="updateBirthdate">@T["Birth Date"]</label>
|
<label class="form-label" for="updateBirthdate">@T["Birth Date"]</label>
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="updateAddressStreetNr">@T["Street Nr."]</label>
|
<label class="form-label" for="updateAddressStreetNr">@T["Street Nr."]</label>
|
||||||
<input type="text" id="updateAddressStreetNr" name="Description.Address.StreetNr" class="form-control" />
|
<input type="number" id="updateAddressStreetNr" name="Description.Address.StreetNr" class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-3">
|
<hr class="my-3">
|
||||||
<h4 class="fw-bold">@T["Workplace & account"]</h4>
|
<h4 class="fw-bold">@T["Workplace & account"]</h4>
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="updatePassword">@T["New Password"]</label>
|
<label class="form-label" for="updatePassword">@T["New Password"]</label>
|
||||||
<input type="password" id="updatePassword" name="UserPassword" class="form-control" />
|
<input type="password" id="updatePassword" name="UserPassword" class="form-control" pattern="(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}" title="@T["Password must be at least 8 characters long and include upper, lower, number, and special character"]" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="updatePhotoFile">@T["Photo"]</label>
|
<label class="form-label" for="updatePhotoFile">@T["Photo"]</label>
|
||||||
@@ -278,6 +278,9 @@
|
|||||||
var dataFromEntries = Object.fromEntries(new FormData(updateForm).entries());
|
var dataFromEntries = Object.fromEntries(new FormData(updateForm).entries());
|
||||||
var data = unflatten(dataFromEntries);
|
var data = unflatten(dataFromEntries);
|
||||||
data.Description.Groups = Array.from(updateForm.querySelector('#updateGroups').selectedOptions).map(option => option.value);
|
data.Description.Groups = Array.from(updateForm.querySelector('#updateGroups').selectedOptions).map(option => option.value);
|
||||||
|
if (data.Description.Address.StreetNr == "") {
|
||||||
|
delete(data.Description.Address.StreetNr);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/Users/Update', {
|
const response = await fetch('/Users/Update', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -434,12 +437,12 @@
|
|||||||
<input type="text" name="Title" id="createTitle" class="form-control" />
|
<input type="text" name="Title" id="createTitle" class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="createName">@T["Name"]</label>
|
<label class="form-label" for="createName">@T["Name"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" name="Cn" id="createName" class="form-control" />
|
<input type="text" name="Cn" id="createName" aria-required="true" required class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="createSurname">@T["Surname"]</label>
|
<label class="form-label" for="createSurname">@T["Surname"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="text" name="Sn" id="createSurname" class="form-control" />
|
<input type="text" name="Sn" id="createSurname" aria-required="true" required class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="createBirthDate">@T["Birth Date"]</label>
|
<label class="form-label" for="createBirthDate">@T["Birth Date"]</label>
|
||||||
@@ -455,7 +458,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="createStreetNr">@T["Street Nr."]</label>
|
<label class="form-label" for="createStreetNr">@T["Street Nr."]</label>
|
||||||
<input type="text" id="createStreetNr" name="Description.Address.StreetNr" class="form-control" />
|
<input type="number" id="createStreetNr" name="Description.Address.StreetNr" class="form-control" />
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-3">
|
<hr class="my-3">
|
||||||
<h4 class="fw-bold">@T["Workplace & account"]</h4>
|
<h4 class="fw-bold">@T["Workplace & account"]</h4>
|
||||||
@@ -470,8 +473,8 @@
|
|||||||
<select id="createGroups" id="createGroups" name="Description.Groups" class="form-select" multiple></select>
|
<select id="createGroups" id="createGroups" name="Description.Groups" class="form-select" multiple></select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="createPassword">@T["Password"]</label>
|
<label class="form-label" for="createPassword">@T["Password"] <span class="text-danger" aria-hidden="true">*</span></label>
|
||||||
<input type="password" id="createPassword" name="UserPassword" class="form-control" />
|
<input type="password" id="createPassword" name="UserPassword" class="form-control" pattern="(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}" title="@T["Password must be at least 8 characters long and include upper, lower, number, and special character"]" aria-required="true" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label class="form-label" for="createPhotoFile">@T["Photo"]</label>
|
<label class="form-label" for="createPhotoFile">@T["Photo"]</label>
|
||||||
@@ -539,7 +542,9 @@
|
|||||||
const dataFromEntries = Object.fromEntries(new FormData(createForm).entries());
|
const dataFromEntries = Object.fromEntries(new FormData(createForm).entries());
|
||||||
const data = unflatten(dataFromEntries);
|
const data = unflatten(dataFromEntries);
|
||||||
data.Description.Groups = Array.from(createGroupsSelect.selectedOptions).map(o => o.value);
|
data.Description.Groups = Array.from(createGroupsSelect.selectedOptions).map(o => o.value);
|
||||||
|
if (data.Description.Address.StreetNr == "") {
|
||||||
|
delete(data.Description.Address.StreetNr);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/Users/Create', {
|
const response = await fetch('/Users/Create', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -572,7 +577,7 @@
|
|||||||
data-user-birthdate="${data.Description.BirthDate}"
|
data-user-birthdate="${data.Description.BirthDate}"
|
||||||
data-user-address-city="${data.Description.Address.City}"
|
data-user-address-city="${data.Description.Address.City}"
|
||||||
data-user-address-street="${data.Description.Address.Street}"
|
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-workplace="${data.Description?.Workplace || ''}"
|
||||||
data-user-groups='${JSON.stringify(data.Description?.Groups || [])}'
|
data-user-groups='${JSON.stringify(data.Description?.Groups || [])}'
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
@@ -580,7 +585,7 @@
|
|||||||
@T["Update"]
|
@T["Update"]
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-danger btn-delete"
|
<button class="btn btn-sm btn-danger btn-delete"
|
||||||
data-user-id="${result.NewUid || ''}"
|
data-user-id="${result.Uid || ''}"
|
||||||
data-user-name="${data.Cn || ''}"
|
data-user-name="${data.Cn || ''}"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#deleteModal">
|
data-bs-target="#deleteModal">
|
||||||
@@ -661,11 +666,11 @@
|
|||||||
<label class="form-label" for="detailCity">@T["City"]</label>
|
<label class="form-label" for="detailCity">@T["City"]</label>
|
||||||
<input type="text" class="form-control" id="detailCity" value="" disabled />
|
<input type="text" class="form-control" id="detailCity" value="" disabled />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-5">
|
||||||
<label class="form-label" for="detailStreet">@T["Street"]</label>
|
<label class="form-label" for="detailStreet">@T["Street"]</label>
|
||||||
<input type="text" class="form-control" id="detailStreet" value="" disabled />
|
<input type="text" class="form-control" id="detailStreet" value="" disabled />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-3">
|
||||||
<label class="form-label" for="detailStreetNr">@T["Street Nr."]</label>
|
<label class="form-label" for="detailStreetNr">@T["Street Nr."]</label>
|
||||||
<input type="text" class="form-control" id="detailStreetNr" value="" disabled />
|
<input type="text" class="form-control" id="detailStreetNr" value="" disabled />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -75,7 +75,11 @@
|
|||||||
<div class="mt-3 p-2 bg border rounded d-flex justify-content-between align-items-center"
|
<div class="mt-3 p-2 bg border rounded d-flex justify-content-between align-items-center"
|
||||||
data-bs-toggle="collapse"
|
data-bs-toggle="collapse"
|
||||||
data-bs-target="#presetsCollapse"
|
data-bs-target="#presetsCollapse"
|
||||||
style="cursor: pointer;">
|
style="cursor: pointer;"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
aria-controls="presetsCollapse"
|
||||||
|
aria-label="@T["Presets. Press to toggle visibility"]">
|
||||||
|
|
||||||
<h4 class="fw-bold m-0">@T["Presets"]</h4>
|
<h4 class="fw-bold m-0">@T["Presets"]</h4>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
||||||
@@ -88,34 +92,46 @@
|
|||||||
@foreach (var preset in Model.Presets)
|
@foreach (var preset in Model.Presets)
|
||||||
{
|
{
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="border rounded p-3 mb-3" data-preset-id="@(preset.Key)">
|
<div class="border rounded p-3 mb-3 h-100" data-preset-id="@(preset.Key)">
|
||||||
<label class="form-label" for="Presets.@(preset.Key).Key">@T["Preset name"]</label>
|
<label class="form-label" for="Presets.@(preset.Key).Key">@T["Preset name"]</label>
|
||||||
<input class="form-control mb-3" id="Presets.@(preset.Key).Key" name="Presets.@(preset.Key).Key" value="@preset.Key"/>
|
<input class="form-control mb-3" id="Presets.@(preset.Key).Key" name="Presets.@(preset.Key).Key" value="@preset.Key"/>
|
||||||
<label class="form-label">@T["Attributes"]</label>
|
<label class="form-label">@T["Attributes"]</label>
|
||||||
@foreach (var attr in preset.Value.Attribute)
|
<a href="#Presets.@(preset.Key).Delete" class="visually-hidden-focusable">@T["Skip attributes table"]</a>
|
||||||
{
|
<table class="attributes mb-2">
|
||||||
<div class="row mb-2" data-type="attribute">
|
<thead>
|
||||||
<div class="col-md-3 mt-2">
|
<tr>
|
||||||
<input class="form-control"
|
<th class="p-1">@T["Name"]</th>
|
||||||
name="Presets.@(preset.Key).Attribute.@(attr.Key).Key"
|
<th class="p-1">@T["Value"]</th>
|
||||||
value="@attr.Key" placeholder="@T["Name"]" aria-label="@T["Name"]" />
|
<th class="p-1">@T["Delete attribute"]</th>
|
||||||
</div>
|
</tr>
|
||||||
<div class="col-md-5 mt-2">
|
</thead>
|
||||||
<input class="form-control"
|
<tbody class="attributes">
|
||||||
name="Presets.@(preset.Key).Attribute.@(attr.Key).Value"
|
@foreach (var attr in preset.Value.Attribute)
|
||||||
value="@attr.Value" placeholder="@T["Value"]" aria-label="@T["Value"]" />
|
{
|
||||||
</div>
|
<tr data-type="attribute">
|
||||||
<div class="col-md-4 mt-2">
|
<td class="col-md-3 p-1">
|
||||||
<button type="button" class="btn btn-danger" data-type="deleteAttribute">@T["Delete attribute"]</button>
|
<input class="form-control"
|
||||||
</div>
|
name="Presets.@(preset.Key).Attribute.@(attr.Key).Key"
|
||||||
</div>
|
value="@attr.Key" placeholder="@T["Name"]" aria-label="@T["Name"]" />
|
||||||
}
|
</td>
|
||||||
|
<td class="col-md-5 p-1">
|
||||||
|
<input class="form-control"
|
||||||
|
name="Presets.@(preset.Key).Attribute.@(attr.Key).Value"
|
||||||
|
value="@attr.Value" placeholder="@T["Value"]" aria-label="@T["Value"]" />
|
||||||
|
</td>
|
||||||
|
<td class="col-md-4 p-1">
|
||||||
|
<button type="button" class="btn btn-danger" data-type="deleteAttribute">@T["Delete attribute"]</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
<div class="attributes"></div>
|
<div class="attributes"></div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-md-8 mt-2">
|
<div class="col-md-6 mt-2">
|
||||||
<button type="button" class="btn btn-danger" data-type="deletePreset">@T["Delete preset"]</button>
|
<button type="button" class="btn btn-danger" id="Presets.@(preset.Key).Delete" data-type="deletePreset">@T["Delete preset"]</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 ms-auto mt-2">
|
<div class="col-md-6 ms-auto mt-2">
|
||||||
<button type="button" class="btn btn-primary" data-type="addAttribute">@T["Add attribute"]</button>
|
<button type="button" class="btn btn-primary" data-type="addAttribute">@T["Add attribute"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,6 +181,26 @@
|
|||||||
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
|
||||||
|
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');
|
const updateForm = document.getElementById('updateSettings');
|
||||||
updateForm.addEventListener('submit', async e => {
|
updateForm.addEventListener('submit', async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -216,25 +252,25 @@
|
|||||||
function addAttribute(presetContainer, presetKey) {
|
function addAttribute(presetContainer, presetKey) {
|
||||||
const attributeId = crypto.randomUUID();
|
const attributeId = crypto.randomUUID();
|
||||||
|
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('tr');
|
||||||
row.classList.add('row', 'mb-2');
|
row.classList.add('row', 'mb-2');
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<div class="col-md-3">
|
<td class="col-md-3 p-1">
|
||||||
<input class="form-control"
|
<input class="form-control"
|
||||||
name="Presets.${presetKey}.Attribute.${attributeId}.Key"
|
name="Presets.${presetKey}.Attribute.${attributeId}.Key"
|
||||||
value="" />
|
value="" />
|
||||||
</div>
|
</td>
|
||||||
<div class="col-md-5">
|
<td class="col-md-5 p-1">
|
||||||
<input class="form-control"
|
<input class="form-control"
|
||||||
name="Presets.${presetKey}.Attribute.${attributeId}.Value"
|
name="Presets.${presetKey}.Attribute.${attributeId}.Value"
|
||||||
value="" />
|
value="" />
|
||||||
</div>
|
</td>
|
||||||
<div class="col-md-4">
|
<td class="col-md-4 p-1">
|
||||||
<button type="button" class="btn btn-danger btn-delete-attr">
|
<button type="button" class="btn btn-danger btn-delete-attr">
|
||||||
${presetContainer.dataset.deleteAttributeText}
|
${presetContainer.dataset.deleteAttributeText}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
presetContainer.querySelector('.attributes').appendChild(row);
|
presetContainer.querySelector('.attributes').appendChild(row);
|
||||||
@@ -311,29 +347,32 @@
|
|||||||
|
|
||||||
const attributeId = crypto.randomUUID();
|
const attributeId = crypto.randomUUID();
|
||||||
|
|
||||||
const row = document.createElement('div');
|
const row = document.createElement('tr');
|
||||||
row.classList.add('row', 'mb-2');
|
row.classList.add('mb-2');
|
||||||
row.setAttribute('data-type', 'attribute');
|
row.setAttribute('data-type', 'attribute');
|
||||||
|
|
||||||
row.innerHTML = `
|
row.innerHTML = `
|
||||||
<div class="col-md-3">
|
<td class="col-md-3 p-1">
|
||||||
<input class="form-control"
|
<input class="form-control"
|
||||||
name="Presets.${presetKey}.Attribute.${attributeId}.Key"
|
name="Presets.${presetKey}.Attribute.${attributeId}.Key"
|
||||||
value="" />
|
value="" />
|
||||||
</div>
|
</td>
|
||||||
<div class="col-md-5">
|
<td class="col-md-5 p-1">
|
||||||
<input class="form-control"
|
<input class="form-control"
|
||||||
name="Presets.${presetKey}.Attribute.${attributeId}.Value"
|
name="Presets.${presetKey}.Attribute.${attributeId}.Value"
|
||||||
value="" />
|
value="" />
|
||||||
</div>
|
</td>
|
||||||
<div class="col-md-4">
|
<td class="col-md-4 p-1">
|
||||||
<button type="button" class="btn btn-danger" data-type="deleteAttribute">
|
<button type="button" class="btn btn-danger" data-type="deleteAttribute">
|
||||||
${presetContainer.dataset.deleteAttributeText || 'Delete attribute'}
|
${presetContainer.dataset.deleteAttributeText || 'Delete attribute'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
attributesContainer.appendChild(row);
|
attributesContainer.appendChild(row);
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
row.querySelector(".col-md-3 input").focus();
|
||||||
|
});
|
||||||
|
|
||||||
// Delete-Button Eventlistener aktivieren
|
// Delete-Button Eventlistener aktivieren
|
||||||
const deleteButton = row.querySelector('button[data-type="deleteAttribute"]');
|
const deleteButton = row.querySelector('button[data-type="deleteAttribute"]');
|
||||||
@@ -350,7 +389,7 @@
|
|||||||
|
|
||||||
function addDeletePresetEventListener(button) {
|
function addDeletePresetEventListener(button) {
|
||||||
button.addEventListener('click', e => {
|
button.addEventListener('click', e => {
|
||||||
const presetContainer = e.target.closest('.col-md-6');
|
const presetContainer = e.target.parentElement.parentElement.closest('.col-md-6');
|
||||||
if (!presetContainer) return;
|
if (!presetContainer) return;
|
||||||
presetContainer.remove();
|
presetContainer.remove();
|
||||||
});
|
});
|
||||||
@@ -368,17 +407,27 @@
|
|||||||
const presetDiv = document.createElement('div');
|
const presetDiv = document.createElement('div');
|
||||||
presetDiv.classList.add('col-md-6');
|
presetDiv.classList.add('col-md-6');
|
||||||
presetDiv.innerHTML = `
|
presetDiv.innerHTML = `
|
||||||
<div class="border rounded p-3 mb-3" data-preset-id="${presetId}">
|
<div class="border rounded p-3 mb-3 h-100" data-preset-id="${presetId}">
|
||||||
<label class="form-label" for="Presets.${presetId}.Key">@T["Preset name"]</label>
|
<label class="form-label" for="Presets.${presetId}.Key">@T["Preset name"]</label>
|
||||||
<input class="form-control mb-3" id="Presets.${presetId}.Key" name="Presets.${presetId}.Key" value=""/>
|
<input class="form-control mb-3" id="Presets.${presetId}.Key" name="Presets.${presetId}.Key" value=""/>
|
||||||
<label class="form-label">@T["Attributes"]</label>
|
<label class="form-label">@T["Attributes"]</label>
|
||||||
<div class="attributes"></div>
|
<a href="#Presets.${presetId}.Delete" class="visually-hidden-focusable">@T["Skip attributes table"]</a>
|
||||||
|
<table class="mb-2">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="p-1">@T["Name"]</th>
|
||||||
|
<th class="p-1">@T["Value"]</th>
|
||||||
|
<th class="p-1">@T["Delete attribute"]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="attributes"></tbody>
|
||||||
|
</table>
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-4">
|
<div class="col-md-6">
|
||||||
<button type="button" class="btn btn-danger" data-type="deletePreset">Delete preset</button>
|
<button type="button" class="btn btn-danger" id="Presets.${presetId}.Delete" data-type="deletePreset">@T["Delete preset"]</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 ms-auto">
|
<div class="col-md-6 ms-auto">
|
||||||
<button type="button" class="btn btn-primary" data-type="addAttribute">Add attribute</button>
|
<button type="button" class="btn btn-primary" data-type="addAttribute">@T["Add attribute"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -386,7 +435,9 @@
|
|||||||
|
|
||||||
// Einfach ans Ende der Container-Liste hängen
|
// Einfach ans Ende der Container-Liste hängen
|
||||||
presetsButton.parentElement.parentElement.insertBefore(presetDiv, presetsButton.parentElement);
|
presetsButton.parentElement.parentElement.insertBefore(presetDiv, presetsButton.parentElement);
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
presetDiv.querySelector(".form-control.mb-3").focus();
|
||||||
|
});
|
||||||
// Eventlistener für den neuen Preset-Block aktivieren
|
// Eventlistener für den neuen Preset-Block aktivieren
|
||||||
const addAttrBtn = presetDiv.querySelector('button[data-type="addAttribute"]');
|
const addAttrBtn = presetDiv.querySelector('button[data-type="addAttribute"]');
|
||||||
const deletePresetBtn = presetDiv.querySelector('button[data-type="deletePreset"]');
|
const deletePresetBtn = presetDiv.querySelector('button[data-type="deletePreset"]');
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
@inject IConfiguration Configuration
|
@inject IConfiguration Configuration
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = T["User settings"];
|
ViewData["Title"] = T["User settings"];
|
||||||
string barcodeType = Configuration["BarcodeType"] ?? "EAN13";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -33,29 +32,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<h4 class="fw-bold">@T["Personal data"]</h4>
|
<h4 class="fw-bold">@T["Personal data"]</h4>
|
||||||
<div class="row g-3 mb-3">
|
<div class="row g-3 mb-3">
|
||||||
<div class="col-md-1">
|
<div class="col-md-2">
|
||||||
<label class="form-label" for="title">@T["Title"]</label>
|
<label class="form-label" for="title">@T["Title"]</label>
|
||||||
<input type="text" id="title" class="form-control" value="@Model.userModel.Title" readonly/>
|
<input type="text" id="title" class="form-control" value="@Model.userModel.Title" readonly/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-5">
|
||||||
<label class="form-label" for="name">@T["Name"]</label>
|
<label class="form-label" for="name">@T["Name"]</label>
|
||||||
<input type="text" id="name" class="form-control" value="@Model.userModel.Cn" readonly/>
|
<input type="text" id="name" class="form-control" value="@Model.userModel.Cn" readonly/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-5">
|
||||||
<label class="form-label" for="surname">@T["Surname"]</label>
|
<label class="form-label" for="surname">@T["Surname"]</label>
|
||||||
<input type="text" id="surname" class="form-control" value="@Model.userModel.Sn" readonly/>
|
<input type="text" id="surname" class="form-control" value="@Model.userModel.Sn" readonly/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-3 mb-3">
|
<div class="row g-3 mb-3">
|
||||||
<div class="col-md-2">
|
<div class="col-md-4">
|
||||||
|
<label class="form-label" for="detailCity">@T["City"]</label>
|
||||||
|
<input type="text" class="form-control" id="detailCity" value="@Model.userModel.Description?.Address.City" readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-label" for="detailStreet">@T["Street"]</label>
|
||||||
|
<input type="text" class="form-control" id="detailStreet" value="@Model.userModel.Description?.Address.Street" readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label" for="detailStreetNr">@T["Street Nr."]</label>
|
||||||
|
<input type="text" class="form-control" id="detailStreetNr" value="@Model.userModel.Description?.Address.StreetNr" readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
<label class="form-label" for="birthdate">@T["Birth date"]</label>
|
<label class="form-label" for="birthdate">@T["Birth date"]</label>
|
||||||
<input type="text" id="birthdate" class="form-control" value="@Model.userModel.Description?.BirthDate" readonly/>
|
<input type="text" id="birthdate" class="form-control" value="@Model.userModel.Description?.BirthDate" readonly/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label class="form-label" for="address">@T["Address"]</label>
|
|
||||||
<input type="text" id="address" class="form-control" value="@Model.userModel.Description?.BirthDate" readonly/>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<label class="form-label" for="workplace">@T["Workplace"]</label>
|
<label class="form-label" for="workplace">@T["Workplace"]</label>
|
||||||
<input type="text" id="workplace" class="form-control" value="@Model.userModel.Description?.Workplace" readonly/>
|
<input type="text" id="workplace" class="form-control" value="@Model.userModel.Description?.Workplace" readonly/>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,6 +89,11 @@
|
|||||||
showToast('@T["Password must be at least 8 characters long and include upper, lower, number, and special character"]', 'danger');
|
showToast('@T["Password must be at least 8 characters long and include upper, lower, number, and special character"]', 'danger');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const photo = updateForm.querySelector('#updatePhotoFile').value;
|
||||||
|
if (password.length == 0 && photo.length == 0) {
|
||||||
|
showToast('@T["There are no changes to be saved."]', 'danger');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const url = `/Settings/User`;
|
const url = `/Settings/User`;
|
||||||
const dataFromEntries = Object.fromEntries(new FormData(updateForm).entries());
|
const dataFromEntries = Object.fromEntries(new FormData(updateForm).entries());
|
||||||
var data = unflatten(dataFromEntries);
|
var data = unflatten(dataFromEntries);
|
||||||
|
|||||||
@@ -67,7 +67,10 @@
|
|||||||
const asset = json.assetsModel;
|
const asset = json.assetsModel;
|
||||||
assetCard.innerHTML = `
|
assetCard.innerHTML = `
|
||||||
<div class="card-body" data-cn="${asset.Cn}">
|
<div class="card-body" data-cn="${asset.Cn}">
|
||||||
<h4 class="card-title mb-4" style="text-align: center;"><button class="btn btn-sm btn-danger" data-action="remove" data-batchid="${i}" style="float:left;">X</button><strong>Asset ${i + 1}:</strong> ${asset.Name}</h4>
|
<span>
|
||||||
|
<h4 class="card-title mb-4" style="text-align: center;"><strong aria-label="Asset ${i + 1}: ${asset.Name}">Asset ${i + 1}:</strong> ${asset.Name}</h4>
|
||||||
|
<button class="btn btn-sm btn-danger" data-action="remove" data-batchid="${i}" aria-label="@T["Delete entry"]" style="float:left;">X</button>
|
||||||
|
</span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<p><strong>@T["Asset ID"]:</strong> ${asset.Cn}</p>
|
<p><strong>@T["Asset ID"]:</strong> ${asset.Cn}</p>
|
||||||
@@ -87,7 +90,10 @@
|
|||||||
} else {
|
} else {
|
||||||
assetCard.innerHTML = `
|
assetCard.innerHTML = `
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h4 class="card-title mb-4" style="text-align: center;"><button class="btn btn-sm btn-danger" data-action="remove" data-batchid="${i}" style="float:left;">X</button><strong>Asset ${i + 1}:</strong> @T["Empty"]</h4>
|
<span>
|
||||||
|
<h4 class="card-title mb-4" style="text-align: center;"><strong aria-label="Asset ${i + 1}: @T["Empty"]">Asset ${i + 1}:</strong> @T["Empty"]</h4>
|
||||||
|
<button class="btn btn-sm btn-danger" data-action="remove" data-batchid="${i}" aria-label="@T["Delete entry"]" style="float:left;">X</button>
|
||||||
|
</span>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="printPreviewBatchButtons${i}" class="col-md-1 justify-content-end" style="margin-left: auto; width: auto;">
|
<div id="printPreviewBatchButtons${i}" class="col-md-1 justify-content-end" style="margin-left: auto; width: auto;">
|
||||||
</div>
|
</div>
|
||||||
@@ -236,10 +242,14 @@
|
|||||||
|
|
||||||
let cardAtIndex = document.querySelector(`[data-card-index="${index}"]`)
|
let cardAtIndex = document.querySelector(`[data-card-index="${index}"]`)
|
||||||
let cardBodyAtIndex = cardAtIndex.children[0];
|
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 cardAtTarget = document.querySelector(`[data-card-index="${newIndex}"]`)
|
||||||
let cardBodyAtTarget = cardAtTarget.children[0];
|
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);
|
cardAtIndex.insertBefore(cardBodyAtTarget, null);
|
||||||
cardAtTarget.insertBefore(cardBodyAtIndex, null);
|
cardAtTarget.insertBefore(cardBodyAtIndex, null);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
@using Microsoft.AspNetCore.Mvc.Localization
|
||||||
|
@inject IViewLocalizer T
|
||||||
|
|
||||||
<button id="openPrintModal"
|
<button id="openPrintModal"
|
||||||
class="btn btn-primary position-fixed bottom-0 start-0 m-4"
|
class="btn btn-primary position-fixed bottom-0 start-0 m-4"
|
||||||
style="width: 3rem; height: 3rem;z-index: 1000;"
|
style="width: 3rem; height: 3rem;z-index: 1000;"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#printModal"
|
data-bs-target="#printModal"
|
||||||
title="Open Print Page">
|
title="@T["Open Print Page"]">
|
||||||
🖨️
|
🖨️
|
||||||
</button>
|
</button>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
@using Microsoft.AspNetCore.Mvc.Localization
|
@using Microsoft.AspNetCore.Mvc.Localization
|
||||||
@using System.Security.Claims
|
@using System.Security.Claims
|
||||||
|
@using System.Globalization
|
||||||
|
|
||||||
@inject IViewLocalizer T
|
@inject IViewLocalizer T
|
||||||
@{
|
@{
|
||||||
bool IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
bool IsAuthenticated = User.Identity?.IsAuthenticated ?? false;
|
||||||
}
|
}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="description" content="Hardware asset management tool" />
|
<meta name="description" content="Hardware asset management tool" />
|
||||||
@@ -39,7 +41,8 @@
|
|||||||
selectUser: '@T["Select user"]',
|
selectUser: '@T["Select user"]',
|
||||||
selectPreset: '@T["Select preset"]',
|
selectPreset: '@T["Select preset"]',
|
||||||
errorLoadingUsers: '@T["Error loading users"]',
|
errorLoadingUsers: '@T["Error loading users"]',
|
||||||
errorLoadingPresets: '@T["Error loading presets"]'
|
errorLoadingPresets: '@T["Error loading presets"]',
|
||||||
|
closeAlert: '@T["Close alert"]'
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
@@ -133,9 +136,92 @@
|
|||||||
|
|
||||||
<footer class="border-top footer text-muted">
|
<footer class="border-top footer text-muted">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
© 2025 - Berufsschule_HAM
|
© 2025 - Berufsschule_HAM |
|
||||||
|
<a asp-controller="Home" asp-action="Accessibility">@T["Accessibility"]</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<!-- Re-authentication Modal -->
|
||||||
|
<div class="modal fade" id="reAuthModal" tabindex="-1" aria-labelledby="reAuthModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-info text-dark">
|
||||||
|
<h3 class="modal-title" id="detailModalLabel">@T["Extend session duration"]</h3>
|
||||||
|
<button type="button" class="btn-close btn-close-white" style="filter: invert(0);" aria-label="Close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form id="reAuthForm">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p id="reAuthModalLabel" class="text-center">@T["The session expires soon."]<br/>@T["Please authenticate to continue without losing data."]</p>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="reAuthUsername" class="form-label">@T["Username"]</label>
|
||||||
|
<input type="text" class="form-control" id="reAuthUsername" name="Username"
|
||||||
|
value="@User.Identity?.Name" readonly />
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="reAuthPassword" class="form-label">@T["Password"]</label>
|
||||||
|
<input type="password" class="form-control" id="reAuthPassword" name="Password" required />
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger d-none" id="reAuthError"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary">@T["Re-authenticate"]</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Flag so modal only opens once
|
||||||
|
window.reAuthModalOpen = false;
|
||||||
|
|
||||||
|
function openReAuthModal() {
|
||||||
|
window.reAuthModalOpen = true;
|
||||||
|
const reAuthModal = new bootstrap.Modal(document.getElementById('reAuthModal'));
|
||||||
|
reAuthModal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleReAuthModal() {
|
||||||
|
fetch('/Home/RemainingTime')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
let remainingMinutesThreshold = 20;
|
||||||
|
let remainingMinutes = data.remainingMinutes;
|
||||||
|
let triggerMinutes = Math.max(0, remainingMinutes - remainingMinutesThreshold);
|
||||||
|
let triggerMs = triggerMinutes * 60 * 1000;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!window.reAuthModalOpen) {
|
||||||
|
openReAuthModal();
|
||||||
|
}
|
||||||
|
}, triggerMs);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
scheduleReAuthModal();
|
||||||
|
|
||||||
|
$('#reAuthForm').on('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = $(this).serialize();
|
||||||
|
$.post('/Home/Login', formData)
|
||||||
|
.done(function () {
|
||||||
|
window.reAuthModalOpen = false;
|
||||||
|
$('#reAuthModal').modal('hide');
|
||||||
|
})
|
||||||
|
.fail(function (data) {
|
||||||
|
console.log(data);
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(data.responseText, 'text/html');
|
||||||
|
const errorHeading = doc.querySelector("main div div h2");
|
||||||
|
let responseText = data.responseText;
|
||||||
|
document.getElementById("reAuthError").textContent = errorHeading.textContent;
|
||||||
|
$('#reAuthError').removeClass('d-none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
@* <script src="~/lib/jquery/dist/jquery.min.js" defer></script> *@
|
@* <script src="~/lib/jquery/dist/jquery.min.js" defer></script> *@
|
||||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"
|
||||||
crossorigin="anonymous" defer></script>
|
crossorigin="anonymous" defer></script>
|
||||||
|
|||||||
16
src/Views/Shared/_Login.cshtml
Normal file
16
src/Views/Shared/_Login.cshtml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
@using Microsoft.AspNetCore.Mvc.Localization
|
||||||
|
@inject IViewLocalizer T
|
||||||
|
|
||||||
|
<form method="post" action="/Home/Login" class="mt-4" style="max-width: 400px; margin: auto;">
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="username" class="form-label">@T["Username"]</label>
|
||||||
|
<input autofocus type="text" class="form-control" id="username" name="username" autocomplete="username" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mb-3">
|
||||||
|
<label for="password" class="form-label">@T["Password"]</label>
|
||||||
|
<input type="password" class="form-control" id="password" name="password" autocomplete="current-password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100">@T["Login"]</button>
|
||||||
|
</form>
|
||||||
@@ -83,9 +83,34 @@ h4.fw-bold, h4.card-title {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[readonly] {
|
[data-bs-theme="dark"] input[readonly] {
|
||||||
background-color: #343a40 !important;
|
background-color: #343a40 !important;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
border-color: var(--bs-border-color) !important;
|
border-color: var(--bs-border-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="light"] input[readonly] {
|
||||||
|
background-color: var(--bs-secondary-bg) !important;
|
||||||
|
box-shadow: none;
|
||||||
|
opacity: 1;
|
||||||
|
border-color: var(--bs-border-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Groups view checkmarks */
|
||||||
|
td.text-success span, td.text-danger span {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make form-check-label WCAG 2.2 (2.5.5) compliant */
|
||||||
|
.form-check {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 44px;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
@@ -2,6 +2,16 @@
|
|||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.id = 'toastContainer';
|
container.id = 'toastContainer';
|
||||||
container.className = 'toast-container position-fixed bottom-0 end-0 p-3';
|
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);
|
document.body.appendChild(container);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
@@ -12,14 +22,26 @@ function showToast(message, type) {
|
|||||||
const toast = document.createElement('div');
|
const toast = document.createElement('div');
|
||||||
toast.className = `toast align-items-center text-white bg-${type} border-0`;
|
toast.className = `toast align-items-center text-white bg-${type} border-0`;
|
||||||
toast.role = 'alert';
|
toast.role = 'alert';
|
||||||
|
var useDarkElements = type === "warning"
|
||||||
toast.innerHTML = `
|
toast.innerHTML = `
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="toast-body">${message}</div>
|
<div class="toast-body">${message}</div>
|
||||||
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
<button type="button" class="btn-close${useDarkElements ? "" : " btn-close-white"} me-2 m-auto"${useDarkElements ? ' style="filter: unset;"' : ""} data-bs-dismiss="toast" aria-label="${window.appTranslations.closeAlert}"></button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
if (useDarkElements) {
|
||||||
|
toast.classList.remove("text-white");
|
||||||
|
toast.classList.add("text-dark");
|
||||||
|
}
|
||||||
toastContainer.appendChild(toast);
|
toastContainer.appendChild(toast);
|
||||||
const bsToast = new bootstrap.Toast(toast, { delay: 3000 });
|
|
||||||
|
const liveRegion = document.getElementById('toastLiveRegion');
|
||||||
|
if (liveRegion) {
|
||||||
|
liveRegion.textContent = '';
|
||||||
|
setTimeout(() => liveRegion.textContent = message, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bsToast = new bootstrap.Toast(toast, { delay: 10000 });
|
||||||
bsToast.show();
|
bsToast.show();
|
||||||
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user