diff --git a/Fuchs/Controllers/IntranetController.Admin.cs b/Fuchs/Controllers/IntranetController.Admin.cs index 0eff37d..0865229 100644 --- a/Fuchs/Controllers/IntranetController.Admin.cs +++ b/Fuchs/Controllers/IntranetController.Admin.cs @@ -1,12 +1,8 @@ -using System.Web; using Fuchs.Services; using Microsoft.AspNetCore.Mvc; -using Microsoft.Data.SqlClient; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using OCORE.SQL; -using static OCORE.commons; -using static OCORE.OCORE_dictionaries; using static OCORE.SQL.sql; using static OCORE.web.mvc_helper_async; @@ -19,15 +15,12 @@ namespace Fuchs.Controllers; // but every data endpoint here ALSO enforces the level server-side (defense in depth) — the // passive/probe data is diagnostic and must never be reachable by a lower-privileged session. // -// The user-management endpoints ("users"/"userpwdreset"/"useremail"/"usergroups"/"usergroupset") -// below share that same fds_sys > 4 gate, but the fis_admin_* stored procs/functions they call -// enforce a SEPARATE, independent authorization axis: the per-user "intranet_admin" setting via -// fis_admin_getUserAuth(@authaccount) > 1. An account with fds_sys > 4 but no intranet_admin -// setting will reach these endpoints yet get empty results / no-op writes from SQL. See -// Docs/Decisions for the ADR covering this — accounts doing user management need BOTH. +// The separate "usr" module (IntranetController.AdminUsers.cs, fis.usr.js) handles user +// management and shares the same fds_sys > 4 gate/AdminJson/GetSystemAdminAuthAsync helpers +// defined here — see Docs/Decisions/0015-split-admin-user-management-module.md. public partial class IntranetController { - // fds_sys authorization must exceed this to use the Admin module. + // fds_sys authorization must exceed this to use the Admin/AdminUsers modules. private const int AdminMinAuthExclusive = 4; private async Task Do_Process_Admin(string fn, string id, string code) @@ -83,183 +76,6 @@ public partial class IntranetController return AdminJson(new { result }); } - case "users": - { - var res = await getSQLDatatable_async( - "SELECT * FROM [dbo].[fis_admin_getUserAccountList](@authaccount, NULL, NULL, NULL) ORDER BY [order];", - _intranet.Intranet__SQLConnectionString, - StdParamlist(SQL_VarChar("@authaccount", UserAccountID)), - Security: DbSec, options: SqlOpt(fn, id, code)); - return AdminJson(new { users = res.DataTable.toArrayofObjectDictionaries() }); - } - - case "userpwdreset": - { - if (!HasForm("useraccount_id")) return BadRequest400(); - string targetId = Form("useraccount_id"); - string newPassword = Form("newpassword"); - if (!string.IsNullOrWhiteSpace(newPassword) && !newPassword.ValidatePassword(minLength: 6, numSpecial: 0)) - { - _logger.LogWarning("Admin userpwdreset: password requirements not met, target={Target} user={User}", - targetId, UserAccountID); - return StatusCode(409, new { error = "requirements" }); - } - - bool success; - string generatedPassword; - try - { - var pl = StdParamlist( - SQL_VarChar("@authaccount", UserAccountID), - SQL_VarChar("@useraccount_id", targetId), - SQL_NVarChar("@newpassword", newPassword, dbNull_IfEmpty: true)); - var res = await getSQLDatatable_async( - "EXECUTE [dbo].[fis_admin_resetUserPassword] @authaccount, @useraccount_id, @enc_key, @newpassword;", - _intranet.Intranet__SQLConnectionString, pl, - Security: DbSec, options: SqlOpt(fn, id, code)); - var row = res.FirstRow; - success = row["success"] is true; - generatedPassword = row.nz("newpassword"); - } - catch (Exception ex) - { - await NotifyAdminUserActionFailedAsync("Passwort-Reset fehlgeschlagen", - $"Der Passwort-Reset für Benutzerkonto {targetId} konnte nicht durchgeführt werden.", - targetId, ex); - return StatusCode(500, new { error = "reset_failed" }); - } - - if (!success) - { - _logger.LogWarning("Admin userpwdreset not successful target={Target} user={User}", targetId, UserAccountID); - return StatusCode(500, new { error = "reset_failed" }); - } - - var userRow = (await getSQLDatatable_async( - "SELECT * FROM [dbo].[fis_admin_getUserAccountList](@authaccount, @useraccount_id, NULL, NULL);", - _intranet.Intranet__SQLConnectionString, - StdParamlist(SQL_VarChar("@authaccount", UserAccountID), SQL_VarChar("@useraccount_id", targetId)), - Security: DbSec, options: SqlOpt(fn, id, code))).FirstRow; - string targetEmail = userRow.nz("email"); - string targetName = $"{userRow.nz("firstname")} {userRow.nz("name")}".Trim(); - - var (subject, html) = BuildPasswordResetEmail(targetName, generatedPassword); - bool emailed = targetEmail.Length > 5 && - await _comService.SendEmailAsync("pwreset_" + targetEmail, subject, html, targetEmail, targetName, null); - - if (!emailed) - { - _logger.LogWarning( - "Admin userpwdreset: password not delivered target={Target} hasEmail={HasEmail} user={User}", - targetId, targetEmail.Length > 5, UserAccountID); - await _events.UserIssueAsync("Passwort-Reset: E-Mail-Versand fehlgeschlagen", - $"Das neue Passwort für Benutzerkonto {targetId} konnte nicht per E-Mail zugestellt werden.", - UserAccountID, new Dictionary { ["target"] = targetId, ["email"] = targetEmail }); - } - - _logger.LogInformation("Admin userpwdreset succeeded target={Target} emailed={Emailed} user={User}", - targetId, emailed, UserAccountID); - return AdminJson(new { ok = true, emailed }); - } - - case "useremail": - { - if (!HasForm("useraccount_id", "email")) return BadRequest400(); - string targetId = Form("useraccount_id"); - string newEmail = Form("email"); - - var currentRow = (await getSQLDatatable_async( - "SELECT * FROM [dbo].[fis_admin_getUserAccountList](@authaccount, @useraccount_id, NULL, NULL);", - _intranet.Intranet__SQLConnectionString, - StdParamlist(SQL_VarChar("@authaccount", UserAccountID), SQL_VarChar("@useraccount_id", targetId)), - Security: DbSec, options: SqlOpt(fn, id, code))).FirstRow; - if (string.IsNullOrEmpty(currentRow.nz("useraccount_id"))) - return BadRequest400(); - - var pl = StdParamlist( - SQL_VarChar("@authaccount", UserAccountID), - SQL_VarChar("@useraccount_id", targetId), - SQL_NVarChar("@name", currentRow.nz("name")), - SQL_NVarChar("@firstname", currentRow.nz("firstname"), dbNull_IfEmpty: true), - SQL_VarChar("@title", currentRow.nz("title"), dbNull_IfEmpty: true), - SQL_VarChar("@gender", currentRow.nz("gender"), dbNull_IfEmpty: true), - SQL_VarChar("@email", newEmail), - SQL_VarChar("@mobile", currentRow.nz("mobile"), dbNull_IfEmpty: true), - SQL_NVarChar("@password", null, dbNull_IfEmpty: true)); - - try - { - await getSQLDatatable_async( - "EXECUTE [dbo].[fis_admin_setUserAccount] @authaccount, @useraccount_id, @name, @firstname, " + - "@title, @gender, @email, @mobile, @password, @enc_key;", - _intranet.Intranet__SQLConnectionString, pl, - Security: DbSec, options: SqlOpt(fn, id, code)); - } - catch (SqlException ex) when (ex.Number == 60001) - { - _logger.LogWarning("Admin useremail: duplicate email rejected target={Target} user={User}", - targetId, UserAccountID); - return StatusCode(409, new { error = "duplicate_email" }); - } - catch (Exception ex) - { - await NotifyAdminUserActionFailedAsync("E-Mail-Änderung fehlgeschlagen", - $"Die E-Mail-Adresse für Benutzerkonto {targetId} konnte nicht geändert werden.", - targetId, ex); - return StatusCode(500, new { error = "change_failed" }); - } - - _logger.LogInformation("Admin useremail changed target={Target} user={User}", targetId, UserAccountID); - return AdminJson(new { ok = true }); - } - - case "usergroups": - { - if (!HasForm("useraccount_id")) return BadRequest400(); - var res = await getSQLDatatable_async( - "SELECT * FROM [dbo].[fis_admin_getUserGroupMembership](@authaccount, @useraccount_id) ORDER BY [order];", - _intranet.Intranet__SQLConnectionString, - StdParamlist( - SQL_VarChar("@authaccount", UserAccountID), - SQL_VarChar("@useraccount_id", Form("useraccount_id"))), - Security: DbSec, options: SqlOpt(fn, id, code)); - return AdminJson(new { groups = res.DataTable.toArrayofObjectDictionaries() }); - } - - case "usergroupset": - { - if (!HasForm("useraccount_id", "usergroup_id", "member")) return BadRequest400(); - string targetId = Form("useraccount_id"); - string groupId = Form("usergroup_id"); - bool member = Form("member").Equals("true", StringComparison.OrdinalIgnoreCase) || Form("member") == "1"; - - var pl = StdParamlist( - SQL_VarChar("@authaccount", UserAccountID), - SQL_VarChar("@usergroup_id", groupId), - SQL_VarChar("@useraccount_id", targetId)); - try - { - var res = member - ? await getSQLDatatable_async( - "EXECUTE [dbo].[fis_admin_addGroupMember] @authaccount, @usergroup_id, @useraccount_id;", - _intranet.Intranet__SQLConnectionString, pl, Security: DbSec, options: SqlOpt(fn, id, code)) - : await getSQLDatatable_async( - "EXECUTE [dbo].[fis_admin_remGroupMember] @authaccount, @usergroup_id, @useraccount_id;", - _intranet.Intranet__SQLConnectionString, pl, Security: DbSec, options: SqlOpt(fn, id, code)); - - _logger.LogInformation("Admin usergroupset target={Target} group={Group} member={Member} user={User}", - targetId, groupId, member, UserAccountID); - return AdminJson(new { ok = true, member = res.FirstRow[0] is true }); - } - catch (Exception ex) - { - await NotifyAdminUserActionFailedAsync("Gruppenzuordnung fehlgeschlagen", - $"Die Gruppenzuordnung für Benutzerkonto {targetId} konnte nicht geändert werden.", - targetId, ex); - return StatusCode(500, new { error = "change_failed" }); - } - } - default: _logger.LogWarning("Admin: no handler for action={Action}, user={User}", id, UserAccountID); return BadRequest400(); @@ -268,6 +84,7 @@ public partial class IntranetController // The status DTOs (SystemInfoSnapshot / SystemProbeResult) are PascalCase; serialize them // camelCase so the Admin frontend contract matches the lowercase convention used elsewhere. + // Shared with IntranetController.AdminUsers.cs. private static readonly JsonSerializerSettings CamelCaseJson = new() { ContractResolver = new CamelCasePropertyNamesContractResolver(), @@ -275,36 +92,15 @@ public partial class IntranetController // Mirror OCORE's getJSONResult exactly (application/json; charset=utf-8) so the response // parses identically to every other endpoint the frontend's postXT talks to. + // Shared with IntranetController.AdminUsers.cs. private ContentResult AdminJson(object payload) => Content(JsonConvert.SerializeObject(payload, CamelCaseJson), "application/json; charset=utf-8"); - /// - /// Builds the subject/HTML body for the "your password was reset by an admin" notification. - /// Extracted as pure logic (no DB/HTTP) so the content — including HTML-encoding of the - /// password — is unit-testable without instantiating the controller. - /// - internal static (string Subject, string Html) BuildPasswordResetEmail(string targetName, string newPassword) => ( - "sanitaerfuchs.de Intranet Passwort zurückgesetzt", - $"

Guten Tag {targetName},
Ihr Passwort wurde von einem Administrator zurückgesetzt.
" + - $"Ihr neues Passwort: {HttpUtility.HtmlEncode(newPassword)}

"); - - /// - /// Logs and raises a user-facing notification for an admin user-management action that - /// threw unexpectedly, per the "interrupting exceptions notify the user" rule - /// () — - /// the admin performing the action must never be left believing it silently succeeded. - /// - private async Task NotifyAdminUserActionFailedAsync(string title, string message, string targetId, Exception ex) - { - _logger.LogError(ex, "{Title} target={Target} user={User}", title, targetId, UserAccountID); - await _events.UserIssueAsync(title, message, UserAccountID, - new Dictionary { ["target"] = targetId }); - } - /// /// Resolves the calling user's fds_sys module authorization level via the /// fis_getModuleAuth SQL function (same mechanism as ). - /// Returns -3 when it cannot be determined (fail-closed). + /// Returns -3 when it cannot be determined (fail-closed). Shared with + /// IntranetController.AdminUsers.cs. /// private async Task GetSystemAdminAuthAsync(string fn, string id, string code) { diff --git a/Fuchs/Controllers/IntranetController.AdminUsers.cs b/Fuchs/Controllers/IntranetController.AdminUsers.cs new file mode 100644 index 0000000..1cf31fe --- /dev/null +++ b/Fuchs/Controllers/IntranetController.AdminUsers.cs @@ -0,0 +1,253 @@ +using System.Web; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Data.SqlClient; +using OCORE.SQL; +using static OCORE.commons; +using static OCORE.OCORE_dictionaries; +using static OCORE.SQL.sql; +using static OCORE.web.mvc_helper_async; + +namespace Fuchs.Controllers; + +// Partial class: Administration user-management module (fn=usr). +// +// Split out of the Admin module (see Docs/Decisions/0015-split-admin-user-management-module.md, +// which superseded 0014) so status diagnostics and user management have separate sidemenu +// buttons. Shares the Admin module's fds_sys > 4 gate (AdminMinAuthExclusive, +// GetSystemAdminAuthAsync, AdminJson — all in IntranetController.Admin.cs). +// +// The fis_admin_* stored procs/functions called below enforce a SEPARATE, independent +// authorization axis: the per-user "intranet_admin" setting via +// fis_admin_getUserAuth(@authaccount) > 1. An account with fds_sys > 4 but no intranet_admin +// setting will reach these endpoints yet get empty results / no-op writes from SQL — accounts +// doing user management need BOTH. +public partial class IntranetController +{ + private async Task Do_Process_AdminUsers(string fn, string id, string code) + { + _logger.LogDebug("Do_Process_AdminUsers action={Action} code={Code} user={User}", id, code, UserAccountID); + + int authLevel = await GetSystemAdminAuthAsync(fn, id, code); + bool authorized = authLevel > AdminMinAuthExclusive; + + if (id.Equals("auth", StringComparison.OrdinalIgnoreCase)) + return await JSONAsync(new { manage = authorized ? 1 : 0, level = authLevel }); + + if (!authorized) + { + _logger.LogWarning("AdminUsers access denied for user={User} (fds_sys={Level}) action={Action}", + UserAccountID, authLevel, id); + return Unauthorized401(); + } + + switch (id.ToLowerInvariant()) + { + case "list": + { + var res = await getSQLDatatable_async( + "SELECT * FROM [dbo].[fis_admin_getUserAccountList](@authaccount, NULL, NULL, NULL) ORDER BY [order];", + _intranet.Intranet__SQLConnectionString, + StdParamlist(SQL_VarChar("@authaccount", UserAccountID)), + Security: DbSec, options: SqlOpt(fn, id, code)); + return AdminJson(new { users = res.DataTable.toArrayofObjectDictionaries() }); + } + + case "pwdreset": + { + if (!HasForm("useraccount_id")) return BadRequest400(); + string targetId = Form("useraccount_id"); + string newPassword = Form("newpassword"); + // Only a plain minimum length is advertised to the admin (see u_pwd_hint) — don't + // silently also demand ValidatePassword's default upper/lower/digit composition. + if (!string.IsNullOrWhiteSpace(newPassword) && + !newPassword.ValidatePassword(minLength: 6, numUpper: 0, numLower: 0, numNumbers: 0, numSpecial: 0)) + { + _logger.LogWarning("AdminUsers pwdreset: password requirements not met, target={Target} user={User}", + targetId, UserAccountID); + return StatusCode(409, new { error = "requirements" }); + } + + bool success; + string generatedPassword; + try + { + var pl = StdParamlist( + SQL_VarChar("@authaccount", UserAccountID), + SQL_VarChar("@useraccount_id", targetId), + SQL_NVarChar("@newpassword", newPassword, dbNull_IfEmpty: true)); + var res = await getSQLDatatable_async( + "EXECUTE [dbo].[fis_admin_resetUserPassword] @authaccount, @useraccount_id, @enc_key, @newpassword;", + _intranet.Intranet__SQLConnectionString, pl, + Security: DbSec, options: SqlOpt(fn, id, code)); + var row = res.FirstRow; + success = row["success"] is true; + generatedPassword = row.nz("newpassword"); + } + catch (Exception ex) + { + await NotifyAdminUserActionFailedAsync("Passwort-Reset fehlgeschlagen", + $"Der Passwort-Reset für Benutzerkonto {targetId} konnte nicht durchgeführt werden.", + targetId, ex); + return StatusCode(500, new { error = "reset_failed" }); + } + + if (!success) + { + _logger.LogWarning("AdminUsers pwdreset not successful target={Target} user={User}", targetId, UserAccountID); + return StatusCode(500, new { error = "reset_failed" }); + } + + var userRow = (await getSQLDatatable_async( + "SELECT * FROM [dbo].[fis_admin_getUserAccountList](@authaccount, @useraccount_id, NULL, NULL);", + _intranet.Intranet__SQLConnectionString, + StdParamlist(SQL_VarChar("@authaccount", UserAccountID), SQL_VarChar("@useraccount_id", targetId)), + Security: DbSec, options: SqlOpt(fn, id, code))).FirstRow; + string targetEmail = userRow.nz("email"); + string targetName = $"{userRow.nz("firstname")} {userRow.nz("name")}".Trim(); + + var (subject, html) = BuildPasswordResetEmail(targetName, generatedPassword); + bool emailed = targetEmail.Length > 5 && + await _comService.SendEmailAsync("pwreset_" + targetEmail, subject, html, targetEmail, targetName, null); + + if (!emailed) + { + _logger.LogWarning( + "AdminUsers pwdreset: password not delivered target={Target} hasEmail={HasEmail} user={User}", + targetId, targetEmail.Length > 5, UserAccountID); + await _events.UserIssueAsync("Passwort-Reset: E-Mail-Versand fehlgeschlagen", + $"Das neue Passwort für Benutzerkonto {targetId} konnte nicht per E-Mail zugestellt werden.", + UserAccountID, new Dictionary { ["target"] = targetId, ["email"] = targetEmail }); + } + + _logger.LogInformation("AdminUsers pwdreset succeeded target={Target} emailed={Emailed} user={User}", + targetId, emailed, UserAccountID); + return AdminJson(new { ok = true, emailed }); + } + + case "email": + { + if (!HasForm("useraccount_id", "email")) return BadRequest400(); + string targetId = Form("useraccount_id"); + string newEmail = Form("email"); + + var currentRow = (await getSQLDatatable_async( + "SELECT * FROM [dbo].[fis_admin_getUserAccountList](@authaccount, @useraccount_id, NULL, NULL);", + _intranet.Intranet__SQLConnectionString, + StdParamlist(SQL_VarChar("@authaccount", UserAccountID), SQL_VarChar("@useraccount_id", targetId)), + Security: DbSec, options: SqlOpt(fn, id, code))).FirstRow; + if (string.IsNullOrEmpty(currentRow.nz("useraccount_id"))) + return BadRequest400(); + + var pl = StdParamlist( + SQL_VarChar("@authaccount", UserAccountID), + SQL_VarChar("@useraccount_id", targetId), + SQL_NVarChar("@name", currentRow.nz("name")), + SQL_NVarChar("@firstname", currentRow.nz("firstname"), dbNull_IfEmpty: true), + SQL_VarChar("@title", currentRow.nz("title"), dbNull_IfEmpty: true), + SQL_VarChar("@gender", currentRow.nz("gender"), dbNull_IfEmpty: true), + SQL_VarChar("@email", newEmail), + SQL_VarChar("@mobile", currentRow.nz("mobile"), dbNull_IfEmpty: true), + SQL_NVarChar("@password", null, dbNull_IfEmpty: true)); + + try + { + await getSQLDatatable_async( + "EXECUTE [dbo].[fis_admin_setUserAccount] @authaccount, @useraccount_id, @name, @firstname, " + + "@title, @gender, @email, @mobile, @password, @enc_key;", + _intranet.Intranet__SQLConnectionString, pl, + Security: DbSec, options: SqlOpt(fn, id, code)); + } + catch (SqlException ex) when (ex.Number == 60001) + { + _logger.LogWarning("AdminUsers email: duplicate email rejected target={Target} user={User}", + targetId, UserAccountID); + return StatusCode(409, new { error = "duplicate_email" }); + } + catch (Exception ex) + { + await NotifyAdminUserActionFailedAsync("E-Mail-Änderung fehlgeschlagen", + $"Die E-Mail-Adresse für Benutzerkonto {targetId} konnte nicht geändert werden.", + targetId, ex); + return StatusCode(500, new { error = "change_failed" }); + } + + _logger.LogInformation("AdminUsers email changed target={Target} user={User}", targetId, UserAccountID); + return AdminJson(new { ok = true }); + } + + case "groups": + { + if (!HasForm("useraccount_id")) return BadRequest400(); + var res = await getSQLDatatable_async( + "SELECT * FROM [dbo].[fis_admin_getUserGroupMembership](@authaccount, @useraccount_id) ORDER BY [order];", + _intranet.Intranet__SQLConnectionString, + StdParamlist( + SQL_VarChar("@authaccount", UserAccountID), + SQL_VarChar("@useraccount_id", Form("useraccount_id"))), + Security: DbSec, options: SqlOpt(fn, id, code)); + return AdminJson(new { groups = res.DataTable.toArrayofObjectDictionaries() }); + } + + case "groupset": + { + if (!HasForm("useraccount_id", "usergroup_id", "member")) return BadRequest400(); + string targetId = Form("useraccount_id"); + string groupId = Form("usergroup_id"); + bool member = Form("member").Equals("true", StringComparison.OrdinalIgnoreCase) || Form("member") == "1"; + + var pl = StdParamlist( + SQL_VarChar("@authaccount", UserAccountID), + SQL_VarChar("@usergroup_id", groupId), + SQL_VarChar("@useraccount_id", targetId)); + try + { + var res = member + ? await getSQLDatatable_async( + "EXECUTE [dbo].[fis_admin_addGroupMember] @authaccount, @usergroup_id, @useraccount_id;", + _intranet.Intranet__SQLConnectionString, pl, Security: DbSec, options: SqlOpt(fn, id, code)) + : await getSQLDatatable_async( + "EXECUTE [dbo].[fis_admin_remGroupMember] @authaccount, @usergroup_id, @useraccount_id;", + _intranet.Intranet__SQLConnectionString, pl, Security: DbSec, options: SqlOpt(fn, id, code)); + + _logger.LogInformation("AdminUsers groupset target={Target} group={Group} member={Member} user={User}", + targetId, groupId, member, UserAccountID); + return AdminJson(new { ok = true, member = res.FirstRow[0] is true }); + } + catch (Exception ex) + { + await NotifyAdminUserActionFailedAsync("Gruppenzuordnung fehlgeschlagen", + $"Die Gruppenzuordnung für Benutzerkonto {targetId} konnte nicht geändert werden.", + targetId, ex); + return StatusCode(500, new { error = "change_failed" }); + } + } + + default: + _logger.LogWarning("AdminUsers: no handler for action={Action}, user={User}", id, UserAccountID); + return BadRequest400(); + } + } + + /// + /// Builds the subject/HTML body for the "your password was reset by an admin" notification. + /// Extracted as pure logic (no DB/HTTP) so the content — including HTML-encoding of the + /// password — is unit-testable without instantiating the controller. + /// + internal static (string Subject, string Html) BuildPasswordResetEmail(string targetName, string newPassword) => ( + "sanitaerfuchs.de Intranet Passwort zurückgesetzt", + $"

Guten Tag {targetName},
Ihr Passwort wurde von einem Administrator zurückgesetzt.
" + + $"Ihr neues Passwort: {HttpUtility.HtmlEncode(newPassword)}

"); + + /// + /// Logs and raises a user-facing notification for an admin user-management action that + /// threw unexpectedly, per the "interrupting exceptions notify the user" rule + /// () — + /// the admin performing the action must never be left believing it silently succeeded. + /// + private async Task NotifyAdminUserActionFailedAsync(string title, string message, string targetId, Exception ex) + { + _logger.LogError(ex, "{Title} target={Target} user={User}", title, targetId, UserAccountID); + await _events.UserIssueAsync(title, message, UserAccountID, + new Dictionary { ["target"] = targetId }); + } +} diff --git a/Fuchs/Controllers/IntranetController.cs b/Fuchs/Controllers/IntranetController.cs index a68826e..16b594c 100644 --- a/Fuchs/Controllers/IntranetController.cs +++ b/Fuchs/Controllers/IntranetController.cs @@ -167,6 +167,7 @@ public partial class IntranetController : Microsoft.AspNetCore.Mvc.Controller "rep" => await Do_Process_Reports(fn, id, code), "bam" => await Do_Process_Bankings(fn, id, code), "admin" => await Do_Process_Admin(fn, id, code), + "usr" => await Do_Process_AdminUsers(fn, id, code), "auth" => await HandleAuth(fn, id, code), "spwc" => await HandleSendPasswordCode(fn, id, code), "spw" => await HandleSendPassword(fn, id, code), diff --git a/Fuchs/Docs/Concepts/admin-system-status.md b/Fuchs/Docs/Concepts/admin-system-status.md index c7dfd37..cf051f2 100644 --- a/Fuchs/Docs/Concepts/admin-system-status.md +++ b/Fuchs/Docs/Concepts/admin-system-status.md @@ -9,11 +9,9 @@ applyTo: - "Fuchs/js/intranet/modules/fis.admin*.js" - "Fuchs/js/intranet/modules/fis.admin.scss" - "Fuchs/js/intranet/fis_main_menu.js" - - "Fuchs_Database/dbo/Functions/fis_admin_*.sql" - - "Fuchs_Database/dbo/Stored Procedures/fis_admin_*.sql" relatedDecisions: - "0011-admin-module-system-status.md" - - "0014-admin-user-management.md" + - "0015-split-admin-user-management-module.md" --- # Admin / System-Status module @@ -28,10 +26,13 @@ Access is restricted to users whose `fds_sys` module authorization is greater th ## How it works ### Authorization (two layers) -1. **Menu visibility** — at page load `fis_main_menu.js#addAdminMenuIfAuthorized` calls - `$fis.getAuth('fds_sys')`; only when the level is > 4 does it push the `init:admin` - button into `$ocms.ocmsmenu` and re-render `#mainmenu`. Users below the threshold never - see the button and never fetch the module script. +1. **Menu visibility** — at page load `fis_main_menu.js#addAdminMenuButtonsIfAuthorized` calls + `$fis.getAuth('fds_sys')` once (shared with the separate `usr` user-management module, see + [0015](../Decisions/0015-split-admin-user-management-module.md)); only when the level is > 4 + does it push a single "Administration" dropdown entry into `$ocms.ocmsmenu` — containing this + module's "Systemüberwachung" (`init:admin`) item alongside `usr`'s "Benutzerverwaltung" + (`init:usr`) item — and re-render `#mainmenu`. Users below the threshold never see the + dropdown and never fetch either module's script. 2. **Server-side gate** — every `/do/admin/*` endpoint resolves `fis_getModuleAuth('fds_sys', authuser)` and returns 401 unless it is > 4. The only exception is `admin/auth`, which returns `{ manage: 0 }` for unauthorized users so the @@ -87,46 +88,18 @@ refresh button, the email card carries the test-email dialog. The topbar has an button that reloads the whole snapshot. Admin responses are serialized **camelCase** (the DTOs are PascalCase) so the JS reads `probe.status`, `info.database.server`, etc. -## User management (`users`/`userpwdreset`/`useremail`/`usergroups`/`usergroupset`) -See [0014](../Decisions/0014-admin-user-management.md) for why this lives here and for the -dual-authorization caveat (`fds_sys > 4` gates the endpoints; the SQL layer separately requires -the caller's `intranet_admin` setting to be `> 1`). - -A card below the status grid (`$adm.usersCard`) renders a table of every non-deleted user -account (name, email, active/disabled) via `POST admin/users` → -`fis_admin_getUserAccountList(@authaccount, NULL, NULL, NULL)`. Clicking a row toggles a -per-row ribbon (`$adm.uMn`, replacing the topbar exactly like Banking's `iMn`/`eM` swap) with -three actions: - -- **Passwort zurücksetzen** — a dialog with one optional password field ("leave blank to - auto-generate"). `POST admin/userpwdreset` calls `fis_admin_resetUserPassword` (now takes an - optional `@newpassword`; `NULL` ⇒ the proc auto-generates one as before). On success the - controller re-fetches the target's row (server-trusted email — never client-supplied) and - emails the plaintext password via `IComService.SendEmailAsync` — the admin never sees it. The - email subject/body is built by `IntranetController.BuildPasswordResetEmail` (pure, unit-tested - in `Fuchs.Tests/AdminUserManagementTests.cs`). Failure to reset, or to deliver the email, calls - `IEventService.UserIssueAsync` so the admin gets a visible notification either way. -- **E-Mail ändern** — a dialog with the email field prefilled. `POST admin/useremail` fetches - the current row and calls `fis_admin_setUserAccount` with only the email changed (other fields - passed through unchanged, `@password = NULL` so the update path leaves the password alone). A - duplicate email (`THROW 60001` in the proc) surfaces as an HTTP 409 `{ error: "duplicate_email" }`. -- **Gruppenzugehörigkeit** — a dialog (`$adm.groupsDlg`) listing every group as a checkbox, - populated via `POST admin/usergroups` → the new `fis_admin_getUserGroupMembership` function. - Each checkbox toggle immediately posts `admin/usergroupset`, which calls - `fis_admin_addGroupMember`/`remGroupMember` depending on the new state — no separate save step. - ## Key files -- `Fuchs/Controllers/IntranetController.Admin.cs` — `Do_Process_Admin` dispatch + auth gate + camelCase JSON helper + user-management endpoints. +- `Fuchs/Controllers/IntranetController.Admin.cs` — `Do_Process_Admin` dispatch + shared auth gate/camelCase JSON helper (also used by the separate `usr` module, see [0015](../Decisions/0015-split-admin-user-management-module.md)). - `Fuchs/Services/ISystemStatusService.cs`, `SystemStatusService.cs`, `SystemStatusModels.cs` — diagnostics service + DTOs. - `Fuchs/Services/StartupCheckReport.cs` (`StartupCheckReporter` singleton) + `StartupSelfTestService.cs` — boot self-test result captured for the non-refreshable widget. - `Fuchs/Services/IBlobStorageService.cs` / `AzureBlobStorageService.cs` — `CheckConnectivityAsync` + `BlobConnectivity`. - `Fuchs/js/intranet/modules/fis.admin*.js`, `fis.admin.scss` — the module, texts, styles (bundled via `bdlconfig.json`). -- `Fuchs/js/intranet/fis_main_menu.js` (`addAdminMenuIfAuthorized`) + `fis_main_go.js` — conditional menu button. -- `Fuchs_Database/dbo/Functions/fis_admin_getUserAccountList.sql`, `fis_admin_getUserGroupList.sql`, `fis_admin_getUserGroupMembership.sql` — read-side user/group queries. -- `Fuchs_Database/dbo/Stored Procedures/fis_admin_resetUserPassword.sql`, `fis_admin_setUserAccount.sql`, `fis_admin_addGroupMember.sql`, `fis_admin_remGroupMember.sql` — write-side user/group procs. +- `Fuchs/js/intranet/fis_main_menu.js` (`addAdminMenuButtonsIfAuthorized`) + `fis_main_go.js` — conditional menu buttons (this module and `usr`). - `Fuchs.Tests/SystemStatusServiceTests.cs` — service tests. -- `Fuchs.Tests/AdminUserManagementTests.cs` — password-reset email content tests. + +User account/group management moved to its own module and concept doc — see +[admin-user-management.md](admin-user-management.md). ## Related decisions - [0011 — Admin module gated on `fds_sys` > 4](../Decisions/0011-admin-module-system-status.md) -- [0014 — Admin user management: folded into the `admin` module, dual auth axis, password always emailed](../Decisions/0014-admin-user-management.md) +- [0015 — Split Admin into separate Status/User-Management modules](../Decisions/0015-split-admin-user-management-module.md) diff --git a/Fuchs/Docs/Concepts/admin-user-management.md b/Fuchs/Docs/Concepts/admin-user-management.md new file mode 100644 index 0000000..c35d2ed --- /dev/null +++ b/Fuchs/Docs/Concepts/admin-user-management.md @@ -0,0 +1,105 @@ +--- +status: Active +lastUpdated: 2026-09-09 +applyTo: + - "Fuchs/Controllers/IntranetController.AdminUsers.cs" + - "Fuchs/js/intranet/modules/fis.usr*.js" + - "Fuchs/js/intranet/modules/fis.usr.scss" + - "Fuchs/js/intranet/fis_main_menu.js" + - "Fuchs_Database/dbo/Functions/fis_admin_*.sql" + - "Fuchs_Database/dbo/Stored Procedures/fis_admin_*.sql" +relatedDecisions: + - "0014-admin-user-management.md" + - "0015-split-admin-user-management-module.md" +--- + +# User Management module (`usr`) + +## Summary +The **Benutzerverwaltung** module lets a privileged operator see every non-deleted intranet +user account (name + email + active/disabled), reset a password, change an email address, and +view/change group assignments. It shares its sidemenu-visibility and server-side authorization +gate with the separate Admin/status-dashboard module (`fds_sys > 4`) but is otherwise fully +independent — own `fn=usr`, own frontend bundle, own controller partial. See +[0015](../Decisions/0015-split-admin-user-management-module.md) for why these were split from +one module into two, and [0014](../Decisions/0014-admin-user-management.md) for the original +decisions this one carries forward (password always emailed, dual auth axis, reuse of +`fis_admin_setUserAccount` for email changes). + +## How it works + +### Authorization (two independent axes — read this before granting access) +1. **Menu visibility + `fds_sys` gate** — identical mechanism to the Admin module: + `fis_main_menu.js#addAdminMenuButtonsIfAuthorized` checks `fds_sys > 4` once and, if + authorized, adds a single "Administration" sidemenu dropdown containing this module's + "Benutzerverwaltung" (`init:usr`) entry alongside Admin's "Systemüberwachung" (`init:admin`) + entry. Every `/do/usr/*` endpoint re-checks `fds_sys > 4` server-side + (`Do_Process_AdminUsers`, reusing `AdminMinAuthExclusive`/`GetSystemAdminAuthAsync` from + `IntranetController.Admin.cs`) and 401s otherwise — defense in depth, same as Admin. +2. **Separate SQL-layer gate** — every `fis_admin_*` proc/function called below independently + checks the caller's `intranet_admin` setting via `fis_admin_getUserAuth(@authaccount) > 1` + (a `fis_useraccounts_settings` row, unrelated to `fis_getModuleAuth`/`fis_module_auth`). An + account with `fds_sys > 4` but no `intranet_admin` setting reaches every `usr` endpoint but + gets **empty results / silent no-op writes** from SQL — not an error. **Any account meant to + do user management needs both `fds_sys > 4` and `intranet_admin >= 2`.** + +### Request flow +``` +click "Benutzerverwaltung" → $ocms.init('usr') + POST /do/usr/auth → { manage, level } (manage>0 ⇒ authorized) + load /web/fis.usr.de.js + /web/fis.usr.css + $ocms.usr.init2() → init3() → renderLst() + POST /do/usr/list → { users: [...] } grid of every non-deleted account + row click → per-row ribbon (iMn): Passwort zurücksetzen / E-Mail ändern / Gruppenzugehörigkeit + POST /do/usr/pwdreset {useraccount_id, newpassword?} → { ok, emailed } + POST /do/usr/email {useraccount_id, email} → { ok } | 409 duplicate_email + POST /do/usr/groups {useraccount_id} → { groups: [...] } + POST /do/usr/groupset {useraccount_id, usergroup_id, member} → { ok, member } +``` + +### Backend (`IntranetController.AdminUsers.cs`) +- `list` → `fis_admin_getUserAccountList(@authaccount, NULL, NULL, NULL)`, all non-deleted + accounts with decrypted name/email/disabled flag. +- `pwdreset` → the admin may type a specific password or leave it blank; validated with a plain + `ValidatePassword(minLength: 6, numUpper: 0, numLower: 0, numNumbers: 0, numSpecial: 0)` — no + hidden composition requirements, matching what the UI actually tells the admin (a prior bug + had this call still requiring `OCORE.commons.ValidatePassword`'s default 2-upper/2-lower/ + 2-digit minimums despite the "mind. 6 Zeichen" message). Calls + `fis_admin_resetUserPassword` (takes an optional `@newpassword`; `NULL` ⇒ the proc + auto-generates one). On success, re-fetches the target's row (server-trusted email — never + client-supplied) and emails the plaintext password via `IComService.SendEmailAsync` — **the + admin never sees it**. Email subject/body built by `IntranetController.BuildPasswordResetEmail` + (pure, unit-tested in `Fuchs.Tests/AdminUserManagementTests.cs`). Any unexpected failure (SQL + throw, email non-delivery, no usable email on file) calls `IEventService.UserIssueAsync` so the + admin gets a visible notification either way, per + [0003](../Decisions/0003-interrupting-exceptions-notify-the-user.md). +- `email` → fetches the current row and calls `fis_admin_setUserAccount` with only the email + changed (`@password = NULL`, so the `WHEN MATCHED` update path leaves `password_enc` alone). A + duplicate email (`THROW 60001` in the proc) surfaces as HTTP 409 `{ error: "duplicate_email" }`. +- `groups` → the new `fis_admin_getUserGroupMembership(@authaccount, @useraccount_id)` function + returns every group plus a `member` bit for that one user. +- `groupset` → calls `fis_admin_addGroupMember`/`remGroupMember` depending on the posted + `member` flag — applied immediately per checkbox toggle, no separate save step. + +### Frontend (`fis.usr.js` + `fis.usr_txt_de.js` + `fis.usr.scss`) +A self-contained list+ribbon module modeled on Banking (`fis.bam.js`)'s `eM`/`iMn` pattern +rather than the Admin dashboard's card layout: `renderLst` builds the table via +`$fis.frm_list`/`$$.tblset`; a row click toggles `.selected` and swaps the `#topbar` ribbon +between the base "Neu Laden" button (`eM`) and the three per-row actions (`iMn`). Each action is +a dialog (`pwdResetDlg`/`emailDlg`/`groupsDlg`) using `$ocms.dlgform`/`$ocms.dlg` with a manual +`submit`/`postXT` (not the built-in `success`-URL wiring) so a 409's `error` code can be mapped +to a specific German message via `$uct.err`. + +## Key files +- `Fuchs/Controllers/IntranetController.AdminUsers.cs` — `Do_Process_AdminUsers` dispatch + `BuildPasswordResetEmail` + `NotifyAdminUserActionFailedAsync`. +- `Fuchs/Controllers/IntranetController.Admin.cs` — shared `AdminMinAuthExclusive`/`GetSystemAdminAuthAsync`/`AdminJson` this module reuses. +- `Fuchs/js/intranet/modules/fis.usr.js`, `fis.usr_txt_de.js`, `fis.usr.scss` — the module, texts, styles (bundled via `bdlconfig.json`, `intranet:usr` context). +- `Fuchs/js/intranet/fis_main_menu.js` (`addAdminMenuButtonsIfAuthorized`) + `fis_main_go.js` — conditional menu buttons (this module and `admin`). +- `Fuchs_Database/dbo/Functions/fis_admin_getUserAccountList.sql`, `fis_admin_getUserGroupList.sql`, `fis_admin_getUserGroupMembership.sql` — read-side user/group queries. +- `Fuchs_Database/dbo/Stored Procedures/fis_admin_resetUserPassword.sql`, `fis_admin_setUserAccount.sql`, `fis_admin_addGroupMember.sql`, `fis_admin_remGroupMember.sql` — write-side user/group procs. +- `Fuchs.Tests/AdminUserManagementTests.cs` — password-reset email content tests. + +## Related decisions +- [0014 — Admin user management (original decisions, still in force)](../Decisions/0014-admin-user-management.md) +- [0015 — Split Admin into separate Status/User-Management modules](../Decisions/0015-split-admin-user-management-module.md) +- [0003 — Interrupting exceptions notify the user](../Decisions/0003-interrupting-exceptions-notify-the-user.md) diff --git a/Fuchs/Docs/Decisions/0014-admin-user-management.md b/Fuchs/Docs/Decisions/0014-admin-user-management.md index 955a794..874e62a 100644 --- a/Fuchs/Docs/Decisions/0014-admin-user-management.md +++ b/Fuchs/Docs/Decisions/0014-admin-user-management.md @@ -1,5 +1,5 @@ --- -status: Accepted +status: Superseded date: 2026-09-09 applyTo: - "Fuchs/Controllers/IntranetController.Admin.cs" @@ -7,7 +7,7 @@ applyTo: - "Fuchs/js/intranet/modules/fis.admin.scss" - "Fuchs_Database/dbo/Functions/fis_admin_*.sql" - "Fuchs_Database/dbo/Stored Procedures/fis_admin_*.sql" -supersededBy: "" +supersededBy: "0015-split-admin-user-management-module.md" --- # 0014 — Admin user management: folded into the `admin` module, dual auth axis, password always emailed diff --git a/Fuchs/Docs/Decisions/0015-split-admin-user-management-module.md b/Fuchs/Docs/Decisions/0015-split-admin-user-management-module.md new file mode 100644 index 0000000..4428687 --- /dev/null +++ b/Fuchs/Docs/Decisions/0015-split-admin-user-management-module.md @@ -0,0 +1,82 @@ +--- +status: Accepted +date: 2026-09-09 +applyTo: + - "Fuchs/Controllers/IntranetController.Admin.cs" + - "Fuchs/Controllers/IntranetController.AdminUsers.cs" + - "Fuchs/js/intranet/modules/fis.admin*.js" + - "Fuchs/js/intranet/modules/fis.usr*.js" + - "Fuchs/js/intranet/modules/fis.usr.scss" + - "Fuchs/js/intranet/fis_main_menu.js" + - "Fuchs_Database/dbo/Functions/fis_admin_*.sql" + - "Fuchs_Database/dbo/Stored Procedures/fis_admin_*.sql" +supersededBy: "" +--- + +# 0015 — Split Admin into separate "Status" (`admin`) and "User Management" (`usr`) modules, under one "Administration" sidemenu dropdown + +## Context +[0014](0014-admin-user-management.md) folded user management into the existing `admin` module +(one sidemenu button, one `fn`) to avoid provisioning a second admin-ish permission. In practice +this meant one button opened two unrelated things — infrastructure diagnostics and user +account administration. The user asked for these to be split into two distinct buttons, grouped +under a single "Administration" sidemenu entry (an `ocmsmenu` dropdown, not two flat top-level +buttons). This decision supersedes 0014's "fold into admin" choice; the parts of 0014 that are +about SQL-layer behavior (dual auth axis, password-reset email, group-toggle semantics) are +unaffected and still apply, just under `fn=usr` instead of `fn=admin`. + +## Decision +- **Two independent modules, exposed as a single "Administration" dropdown in the sidemenu** + with two entries — Benutzerverwaltung and Systemüberwachung — using `ocmsmenu`'s existing + nested-item support (`itm: [...]` on a menu entry renders it as a Bootstrap dropdown; see + `oci_main.js#$ocms.menu`, `subm`/`subi`). Both modules stay gated by the same `fds_sys > 4` + server-side check (`AdminMinAuthExclusive`/`GetSystemAdminAuthAsync`, both defined once in + `IntranetController.Admin.cs` and shared by both dispatchers): + - `fn=admin` — unchanged status/diagnostics dashboard (`Do_Process_Admin`, `fis.admin.js`), + reached via the "Systemüberwachung" dropdown entry. + - `fn=usr` — new user-management module (`Do_Process_AdminUsers` in the new + `IntranetController.AdminUsers.cs`, `fis.usr.js`/`fis.usr_txt_de.js`/`fis.usr.scss`), built + as a self-contained list+ribbon module (Banking's `iMn`/`eM` pattern) rather than a card + embedded in the dashboard, reached via the "Benutzerverwaltung" dropdown entry. +- **The still-true dual-authorization caveat from 0014 carries over unchanged, just renamed**: + `fds_sys > 4` gates the `usr` module's endpoints; the `fis_admin_*` SQL objects they call + separately require the caller's `intranet_admin` setting to be `> 1` + (`fis_admin_getUserAuth`). An account needs both to actually manage users. + `Fuchs/Docs/Concepts/admin-user-management.md` now carries this note (split out of + `admin-system-status.md`). +- `id` values shortened now that the module name itself says "usr" (no more redundant `user` + prefix): `users`→`list`, `userpwdreset`→`pwdreset`, `useremail`→`email`, + `usergroups`→`groups`, `usergroupset`→`groupset`. Endpoints move from `/do/admin/*` to + `/do/usr/*` accordingly. +- One shared client-side auth check gates the whole dropdown: + `fis_main_menu.js#addAdminMenuButtonsIfAuthorized` (renamed from + `addAdminMenuIfAuthorized`) calls `$fis.getAuth('fds_sys')` once and, if authorized, pushes a + **single** parent menu entry (`id: 'm_admin_grp'`, label `$t.m_admin` = "Administration", no + `fnc` of its own) whose `itm` array holds the two real entries (`fnc: 'init:usr'` / + `fnc: 'init:admin'`). The parent has no navigation behavior — clicking it only opens/closes the + dropdown; the two children navigate. Idempotency is checked on the parent id alone. + +## Consequences +- Two bundle pairs now exist for what was one (`fis.admin.de.js`/`.min.css` and + `fis.usr.de.js`/`.min.css`, both registered in `bdlconfig.json`) — a small duplication of the + lazy-load/auth-check plumbing, acceptable for the clarity of separate concerns. +- The in-module header text for the dashboard module changed from "Administration" to + "Systemüberwachung" (`fis.admin_txt_de.js#$adt.mdl`) so it no longer collides with the parent + dropdown's "Administration" label once both are visible on screen at once. +- Any future admin-only feature should default to its own module, added as a new entry under the + same "Administration" dropdown (or its own dropdown, if unrelated), unless it's genuinely part + of the status dashboard's diagnostics — the original "fold everything into one Admin button" + instinct from 0014 didn't hold up once a second real feature existed. +- `IntranetController.BuildPasswordResetEmail`/`NotifyAdminUserActionFailedAsync` moved to + `IntranetController.AdminUsers.cs`; `Fuchs.Tests/AdminUserManagementTests.cs` needed no change + since it calls them through the (unchanged) `IntranetController` type. + +## Alternatives considered +- **Two flat top-level sidemenu buttons (no shared "Administration" parent).** Rejected per + explicit user direction — the request was specifically for both to live "within + Administration" in the sidemenu, i.e. a single grouping entry with two children. +- **Keep one module, add an internal tab/sub-nav for user management.** Rejected — a client-side + tab within one `fn` would still route through one `fn` auth check per the frontend's + per-`fn` auth-caching design (`fis_main.js#$ocms.init`), the same limitation 0014 already ran + into when this was one module with two unrelated concerns; splitting into two real modules + under a shared menu dropdown gets a clean navigation grouping without that limitation. diff --git a/Fuchs/bdlconfig.json b/Fuchs/bdlconfig.json index 4788cc7..d98ed0a 100644 --- a/Fuchs/bdlconfig.json +++ b/Fuchs/bdlconfig.json @@ -170,5 +170,24 @@ "js/intranet/modules/fis.admin.scss" ], "minify": { "enabled": true } + }, + { + "context": "intranet:usr", + "outputFileName": "wwwroot/web/fis.usr.de.js", + "inputFiles": [ + "js/intranet/modules/fis.usr_txt_de.js", + "js/intranet/modules/fis.usr.js" + ], + "minify": { "enabled": true } + }, + { + "context": "intranet:usr", + "outputFileName": "wwwroot/web/fis.usr.min.css", + "inputFiles": [ + "css/intranet/oci_variables.scss", + "css/intranet/fis_variables.scss", + "js/intranet/modules/fis.usr.scss" + ], + "minify": { "enabled": true } } ] diff --git a/Fuchs/js/intranet/fis_main_go.js b/Fuchs/js/intranet/fis_main_go.js index 24a03b1..8d91624 100644 --- a/Fuchs/js/intranet/fis_main_go.js +++ b/Fuchs/js/intranet/fis_main_go.js @@ -2,5 +2,5 @@ $fis.notifications.init(); $fis.draft.init(); $fis.ov(); - $fis.addAdminMenuIfAuthorized(); + $fis.addAdminMenuButtonsIfAuthorized(); }); diff --git a/Fuchs/js/intranet/fis_main_menu.js b/Fuchs/js/intranet/fis_main_menu.js index 3be1d2e..ad4302e 100644 --- a/Fuchs/js/intranet/fis_main_menu.js +++ b/Fuchs/js/intranet/fis_main_menu.js @@ -11,15 +11,23 @@ ]); })(); -/* The Administration module is restricted to users whose fds_sys authorization exceeds 4. - The button is added — and only then does clicking it load the module script — after the - asynchronous auth check resolves, so lower-privileged users never see it or fetch the code. +/* "Administration" is a single sidemenu dropdown (ocmsmenu supports nested items via `itm`) + containing two buttons — Benutzerverwaltung (fn=usr) and Systemüberwachung (fn=admin) — both + restricted to users whose fds_sys authorization exceeds 4. One auth check gates the whole + dropdown; it (and therefore both module scripts) is only added after that check resolves, so + lower-privileged users never see it or fetch either module's code. Called from fis_main_go.js once the DOM (and the initial main menu) is ready. */ -$fis.addAdminMenuIfAuthorized = function () { - if ($('#m_admin').length > 0) { return; } // idempotent +$fis.addAdminMenuButtonsIfAuthorized = function () { + if ($('#m_admin_grp').length > 0) { return; } // idempotent $fis.getAuth('fds_sys').then(function (auth) { - if ((auth || -1) > 4 && $('#m_admin').length === 0) { - $ocms.ocmsmenu.push({ lbl: $t.m_admin, id: 'm_admin', fnc: 'init:admin', ico: 'glyphicon glyphicon-cog' }); + if ((auth || -1) > 4 && $('#m_admin_grp').length === 0) { + $ocms.ocmsmenu.push({ + lbl: $t.m_admin, id: 'm_admin_grp', ico: 'glyphicon glyphicon-cog', + itm: [ + { lbl: $t.m_usr, id: 'm_usr', fnc: 'init:usr', ico: 'glyphicon glyphicon-user' }, + { lbl: $t.m_admin_status, id: 'm_admin', fnc: 'init:admin', ico: 'glyphicon glyphicon-dashboard' } + ] + }); /* Re-render the main menu cleanly: drop the previously generated menu list (never the .nav-right settings dropdown) and rebuild it from the array. */ $('#mainmenu').children('ul:not(.nav-right)').remove(); diff --git a/Fuchs/js/intranet/fis_texts_gui_de.js b/Fuchs/js/intranet/fis_texts_gui_de.js index a1f5336..6784ce6 100644 --- a/Fuchs/js/intranet/fis_texts_gui_de.js +++ b/Fuchs/js/intranet/fis_texts_gui_de.js @@ -5,6 +5,8 @@ , m_todo: 'ToDos' , m_bcd: 'BankBuchungen' , m_admin: 'Administration' + , m_admin_status: 'Systemüberwachung' + , m_usr: 'Benutzerverwaltung' , rsp: 'Passwort ändern' , pnm: 'Die Passwörter stimmen nicht überein' , cps: 'Das neue Passwort wurde gespeichert.' diff --git a/Fuchs/js/intranet/modules/fis.admin.js b/Fuchs/js/intranet/modules/fis.admin.js index ce011d5..5690598 100644 --- a/Fuchs/js/intranet/modules/fis.admin.js +++ b/Fuchs/js/intranet/modules/fis.admin.js @@ -16,14 +16,6 @@ let $adm = { { lbl: $adt.refreshAll, id: 'adm_refresh_all', ico: 'glyphicon glyphicon-refresh', fnc: function () { $adm.init3(); } } ]); }, - /* Per-row ribbon shown while a user row is selected (mirrors the Banking module's iMn/eM swap). */ - uMn: function (rw) { - return $('#topbar').ocmsmenu([ - { lbl: $adt.u_pwdreset, fnc: function () { $adm.pwdResetDlg(rw); } }, - { lbl: $adt.u_email, fnc: function () { $adm.emailDlg(rw); } }, - { lbl: $adt.u_groups, fnc: function () { $adm.groupsDlg(rw); } } - ]); - }, load: function (frm) { $ocms.postXT({ url: $ocms.url('admin/status'), success: function (r) { @@ -50,109 +42,6 @@ let $adm = { $adm.blobCard(grid, info.blob || {}, pmap.blob); $adm.mfrCard(grid, info.mfr || {}, pmap.mfr); $adm.kvCard(grid, info.keyVault || {}, pmap.keyvault); - - $$.dc('adm_sec', frm).text($adt.sub_users); - $adm.usersCard(frm); - }, - - /* ── user management ─────────────────────────────────────────────────── */ - usersCard: function (frm) { - let card = $$.dc('adm_card adm_card_wide', frm); - let hd = $$.dc('adm_card_hd', card); - $$.s($adt.u_title).appendTo(hd); - let bd = $$.dc('adm_card_bd adm_users_bd', card); - $adm.renderUsers(bd); - }, - renderUsers: function (bd) { - bd.empty(); - let ts = $$.tblset({}, bd); - let thr = $$.tr(ts.hd); - $.each([$adt.u_col_name, $adt.u_col_email, $adt.u_col_status], function (i, lbl) { - $$.th(thr).text(lbl); - }); - $ocms.postXT({ - url: $ocms.url('admin/users'), success: function (r) { - $.each(r.users || [], function (ri, rw) { - let tr = $$.tr(ts.bdy, { id: 'adu' + rw.useraccount_id }); - $$.td(tr).text(`${rw.firstname || ''} ${rw.name || ''}`.trim()); - $$.td(tr).text(rw.email || ''); - $$.td(tr).text(bool(rw.disabled, false) === true ? $adt.u_disabled : $adt.u_active); - tr.click(function () { - tr.toggleClass('selected').siblings().rC('selected'); - if (tr.is('.selected') === true) { $adm.uMn(rw); } else { $adm.topbar(); } - }); - }); - }, error: function () { - $$.dc('adm_err', bd).text($adt.loadfail); - } - }); - }, - pwdResetDlg: function (rw) { - $ocms.dlgform([ - { name: 'useraccount_id', type: 'hidden', value: rw.useraccount_id }, - { name: 'newpassword', label: $adt.u_pwd_field, type: 'string', note: $adt.u_pwd_hint } - ], { - title: $adt.u_pwdreset, - button: $adt.u_pwdreset_btn, - submit: function (e) { - let c = $(this).ldng(1); - let qs = c.serializeObject(true, { typedvalues: true }); - $ocms.postXT({ - url: $ocms.url('admin/userpwdreset'), data: qs, success: function (response) { - alert(response.emailed === true ? $adt.u_pwdreset_sent : $adt.u_pwdreset_noemail); - c.trigger('modal_close'); - }, error: function (jqXHR) { - let code = ((jqXHR || {}).responseJSON || {}).error; - alert($adt.u_err[code] || $adt.u_err_generic); - }, complete: function () { c.ldng(0); $adm.renderUsers($('.adm_users_bd')); $adm.topbar(); } - }); - } - }); - }, - emailDlg: function (rw) { - $ocms.dlgform([ - { name: 'useraccount_id', type: 'hidden', value: rw.useraccount_id }, - { name: 'email', label: $adt.u_email_field, type: 'email', required: true, value: rw.email } - ], { - title: $adt.u_email, - submit: function (e) { - let c = $(this).ldng(1); - let qs = c.serializeObject(true, { typedvalues: true }); - $ocms.postXT({ - url: $ocms.url('admin/useremail'), data: qs, success: function () { - c.trigger('modal_close'); - }, error: function (jqXHR) { - let code = ((jqXHR || {}).responseJSON || {}).error; - alert($adt.u_err[code] || $adt.u_err_generic); - }, complete: function () { c.ldng(0); $adm.renderUsers($('.adm_users_bd')); $adm.topbar(); } - }); - } - }); - }, - groupsDlg: function (rw) { - let f = $$.dc('adm_groups_dlg'); - $ocms.postXT({ - url: $ocms.url('admin/usergroups'), data: { useraccount_id: rw.useraccount_id }, success: function (r) { - $.each(r.groups || [], function (gi, g) { - let row = $$.dc('adm_group_row', f); - let cid = 'adg' + g.usergroup_id; - let cb = $$.i({ type: 'checkbox', id: cid }).prop('checked', bool(g.member, false)).appendTo(row); - $$.lbl(g.name, { for: cid }).appendTo(row); - cb.change(function () { - let checked = cb.prop('checked'); - $ocms.postXT({ - url: $ocms.url('admin/usergroupset'), - data: { useraccount_id: rw.useraccount_id, usergroup_id: g.usergroup_id, member: checked }, - error: function () { - cb.prop('checked', !checked); - alert($adt.u_err_generic); - } - }); - }); - }); - } - }); - $ocms.dlg(f, { title: string($adt.u_groups_title, [`${rw.firstname || ''} ${rw.name || ''}`.trim()]), size: [420, 380] }); }, /* ── generic building blocks ─────────────────────────────────────────── */ diff --git a/Fuchs/js/intranet/modules/fis.admin.scss b/Fuchs/js/intranet/modules/fis.admin.scss index c7f1cff..2616245 100644 --- a/Fuchs/js/intranet/modules/fis.admin.scss +++ b/Fuchs/js/intranet/modules/fis.admin.scss @@ -188,29 +188,6 @@ color: #fff; border-color: rgba(255, 255, 255, 0.4); } - - .adm_users_bd table tbody tr { - cursor: pointer; - - &:hover td { background-color: #f3f6fb; } - } -} - -.adm_groups_dlg { - padding: 0.25rem 0; - - .adm_group_row { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.35rem 0; - border-bottom: 1px solid #eee; - - &:last-child { border-bottom: none; } - - input[type="checkbox"] { margin: 0; } - label { margin: 0; font-weight: normal; } - } } .adm_note { diff --git a/Fuchs/js/intranet/modules/fis.admin_txt_de.js b/Fuchs/js/intranet/modules/fis.admin_txt_de.js index d1327d5..1713d6b 100644 --- a/Fuchs/js/intranet/modules/fis.admin_txt_de.js +++ b/Fuchs/js/intranet/modules/fis.admin_txt_de.js @@ -1,5 +1,5 @@ let $adt = { - mdl: 'Administration' + mdl: 'Systemüberwachung' , sub_info: 'System' , sub_status: 'Status & Konnektivität' , sub_tests: 'Tests' @@ -81,29 +81,4 @@ let $adt = { , tm_default_subject: 'Fuchs Intranet – Test-E-Mail' , tm_default_body: 'Dies ist eine Test-E-Mail aus dem Administrationsbereich des Fuchs Intranet.' , loadfail: 'Der Systemstatus konnte nicht geladen werden.' - // user management - , sub_users: 'Benutzerverwaltung' - , u_title: 'Benutzerkonten' - , u_col_name: 'Name' - , u_col_email: 'E-Mail' - , u_col_status: 'Status' - , u_active: 'Aktiv' - , u_disabled: 'Deaktiviert' - , u_pwdreset: 'Passwort zurücksetzen' - , u_pwdreset_btn: 'Zurücksetzen' - , u_pwd_field: 'Neues Passwort' - , u_pwd_hint: 'Leer lassen, um automatisch ein Passwort zu generieren. In jedem Fall wird das neue Passwort per E-Mail an den Benutzer gesendet.' - , u_pwdreset_sent: 'Das neue Passwort wurde per E-Mail an den Benutzer gesendet.' - , u_pwdreset_noemail: 'Das Passwort wurde zurückgesetzt, konnte aber nicht per E-Mail zugestellt werden.' - , u_email: 'E-Mail-Adresse ändern' - , u_email_field: 'E-Mail-Adresse' - , u_groups: 'Gruppenzugehörigkeit' - , u_groups_title: 'Gruppen – {0}' - , u_err: { - requirements: 'Das Passwort erfüllt nicht die Mindestanforderungen (mind. 6 Zeichen).', - duplicate_email: 'Diese E-Mail-Adresse ist bereits einem anderen Benutzerkonto zugeordnet.', - reset_failed: 'Das Passwort konnte nicht zurückgesetzt werden.', - change_failed: 'Die Änderung konnte nicht gespeichert werden.' - } - , u_err_generic: 'Die Änderung konnte nicht gespeichert werden.' }; diff --git a/Fuchs/js/intranet/modules/fis.usr.js b/Fuchs/js/intranet/modules/fis.usr.js new file mode 100644 index 0000000..5c4476c --- /dev/null +++ b/Fuchs/js/intranet/modules/fis.usr.js @@ -0,0 +1,123 @@ +let $usr = { + init2: function () { + // No external libraries needed — go straight to render. + $ocms.getScript([], function () { $usr.init3(); }); + }, + init3: function () { + $fis.cf(true); + $fis.lf(true); + $('#activemodule').text($uct.mdl); + $usr.eM(); + $usr.renderLst(); + }, + /* Default ribbon (nothing selected). */ + eM: function (r) { + let m = []; + if (bool(r, false) === true) { m.push({ lbl: $uct.rel, fnc: $usr.renderLst }); } + return $('#topbar').ocmsmenu(m); + }, + /* Per-row ribbon shown while a user row is selected (mirrors the Banking module's iMn/eM swap). */ + iMn: function (rw) { + return $('#topbar').ocmsmenu([ + { lbl: $uct.pwdreset, fnc: function () { $usr.pwdResetDlg(rw); } }, + { lbl: $uct.email, fnc: function () { $usr.emailDlg(rw); } }, + { lbl: $uct.groups, fnc: function () { $usr.groupsDlg(rw); } } + ]); + }, + renderLst: function () { + let usrlst = $fis.frm_list(true, true).ldng(1), usrfrm = $$.dc('usrfrm', usrlst); + $usr.eM(true); + $ocms.postXT({ + url: $ocms.url('usr/list'), success: function (r) { + usrfrm.empty(); + let ts = $$.tblset({}, usrfrm); + let thr = $$.tr(ts.hd); + $.each([$uct.col_name, $uct.col_email, $uct.col_status], function (i, lbl) { + $$.th(thr).text(lbl); + }); + $.each(r.users || [], function (ri, rw) { + let tr = $$.tr(ts.bdy, { id: 'usr' + rw.useraccount_id }); + $$.td(tr).text(`${rw.firstname || ''} ${rw.name || ''}`.trim()); + $$.td(tr).text(rw.email || ''); + $$.td(tr).text(bool(rw.disabled, false) === true ? $uct.disabled : $uct.active); + tr.click(function () { + tr.toggleClass('selected').siblings().rC('selected'); + if (tr.is('.selected') === true) { $usr.iMn(rw); } else { $usr.eM(true); } + }); + }); + }, error: function () { + usrfrm.empty(); + $$.dc('usr_err', usrfrm).text($uct.loadfail); + }, complete: function () { usrlst.ldng(0); } + }); + }, + pwdResetDlg: function (rw) { + $ocms.dlgform([ + { name: 'useraccount_id', type: 'hidden', value: rw.useraccount_id }, + { name: 'newpassword', label: $uct.pwd_field, type: 'string', note: $uct.pwd_hint } + ], { + title: $uct.pwdreset, + button: $uct.pwdreset_btn, + submit: function (e) { + let c = $(this).ldng(1); + let qs = c.serializeObject(true, { typedvalues: true }); + $ocms.postXT({ + url: $ocms.url('usr/pwdreset'), data: qs, success: function (response) { + alert(response.emailed === true ? $uct.pwdreset_sent : $uct.pwdreset_noemail); + c.trigger('modal_close'); + }, error: function (jqXHR) { + let code = ((jqXHR || {}).responseJSON || {}).error; + alert($uct.err[code] || $uct.err_generic); + }, complete: function () { c.ldng(0); $usr.renderLst(); } + }); + } + }); + }, + emailDlg: function (rw) { + $ocms.dlgform([ + { name: 'useraccount_id', type: 'hidden', value: rw.useraccount_id }, + { name: 'email', label: $uct.email_field, type: 'email', required: true, value: rw.email } + ], { + title: $uct.email, + submit: function (e) { + let c = $(this).ldng(1); + let qs = c.serializeObject(true, { typedvalues: true }); + $ocms.postXT({ + url: $ocms.url('usr/email'), data: qs, success: function () { + c.trigger('modal_close'); + }, error: function (jqXHR) { + let code = ((jqXHR || {}).responseJSON || {}).error; + alert($uct.err[code] || $uct.err_generic); + }, complete: function () { c.ldng(0); $usr.renderLst(); } + }); + } + }); + }, + groupsDlg: function (rw) { + let f = $$.dc('usr_groups_dlg'); + $ocms.postXT({ + url: $ocms.url('usr/groups'), data: { useraccount_id: rw.useraccount_id }, success: function (r) { + $.each(r.groups || [], function (gi, g) { + let row = $$.dc('usr_group_row', f); + let cid = 'usrg' + g.usergroup_id; + let cb = $$.i({ type: 'checkbox', id: cid }).prop('checked', bool(g.member, false)).appendTo(row); + $$.lbl(g.name, { for: cid }).appendTo(row); + cb.change(function () { + let checked = cb.prop('checked'); + $ocms.postXT({ + url: $ocms.url('usr/groupset'), + data: { useraccount_id: rw.useraccount_id, usergroup_id: g.usergroup_id, member: checked }, + error: function () { + cb.prop('checked', !checked); + alert($uct.err_generic); + } + }); + }); + }); + } + }); + $ocms.dlg(f, { title: string($uct.groups_title, [`${rw.firstname || ''} ${rw.name || ''}`.trim()]), size: [420, 380] }); + } +}; +let $$usr = { init2: $usr.init2, auth: {} }; +export default $$usr; diff --git a/Fuchs/js/intranet/modules/fis.usr.scss b/Fuchs/js/intranet/modules/fis.usr.scss new file mode 100644 index 0000000..23991b9 --- /dev/null +++ b/Fuchs/js/intranet/modules/fis.usr.scss @@ -0,0 +1,55 @@ +.usrfrm { + > table { + border-collapse: collapse; + margin: 2rem; + + th { + padding: 0.2rem 0.35rem; + border: 1px solid #CCC; + } + + td { + padding: 0.2rem 0.35rem; + border: 1px solid #CCC; + } + + tr { + cursor: pointer; + + &:nth-child(2n+1) td { + background-color: #EEE; + } + + &:hover td { + background-color: #f3f6fb; + } + + &.selected td { + background-color: lightblue; + } + } + } + + .usr_err { + padding: 1rem 2rem; + color: #b00020; + font-weight: bold; + } +} + +.usr_groups_dlg { + padding: 0.25rem 0; + + .usr_group_row { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0; + border-bottom: 1px solid #eee; + + &:last-child { border-bottom: none; } + + input[type="checkbox"] { margin: 0; } + label { margin: 0; font-weight: normal; } + } +} diff --git a/Fuchs/js/intranet/modules/fis.usr_txt_de.js b/Fuchs/js/intranet/modules/fis.usr_txt_de.js new file mode 100644 index 0000000..5185532 --- /dev/null +++ b/Fuchs/js/intranet/modules/fis.usr_txt_de.js @@ -0,0 +1,27 @@ +let $uct = { + mdl: 'Benutzerverwaltung' + , rel: 'Neu Laden' + , col_name: 'Name' + , col_email: 'E-Mail' + , col_status: 'Status' + , active: 'Aktiv' + , disabled: 'Deaktiviert' + , loadfail: 'Die Benutzerliste konnte nicht geladen werden.' + , pwdreset: 'Passwort zurücksetzen' + , pwdreset_btn: 'Zurücksetzen' + , pwd_field: 'Neues Passwort' + , pwd_hint: 'Leer lassen, um automatisch ein Passwort zu generieren. In jedem Fall wird das neue Passwort per E-Mail an den Benutzer gesendet.' + , pwdreset_sent: 'Das neue Passwort wurde per E-Mail an den Benutzer gesendet.' + , pwdreset_noemail: 'Das Passwort wurde zurückgesetzt, konnte aber nicht per E-Mail zugestellt werden.' + , email: 'E-Mail-Adresse ändern' + , email_field: 'E-Mail-Adresse' + , groups: 'Gruppenzugehörigkeit' + , groups_title: 'Gruppen – {0}' + , err: { + requirements: 'Das Passwort erfüllt nicht die Mindestanforderungen (mind. 6 Zeichen).', + duplicate_email: 'Diese E-Mail-Adresse ist bereits einem anderen Benutzerkonto zugeordnet.', + reset_failed: 'Das Passwort konnte nicht zurückgesetzt werden.', + change_failed: 'Die Änderung konnte nicht gespeichert werden.' + } + , err_generic: 'Die Änderung konnte nicht gespeichert werden.' +}; diff --git a/Fuchs/wwwroot/web/fis.admin.css b/Fuchs/wwwroot/web/fis.admin.css index e458918..f672fe6 100644 --- a/Fuchs/wwwroot/web/fis.admin.css +++ b/Fuchs/wwwroot/web/fis.admin.css @@ -245,33 +245,6 @@ color: #fff; border-color: rgba(255, 255, 255, 0.4); } -.admfrm .adm_users_bd table tbody tr { - cursor: pointer; -} -.admfrm .adm_users_bd table tbody tr:hover td { - background-color: #f3f6fb; -} - -.adm_groups_dlg { - padding: 0.25rem 0; -} -.adm_groups_dlg .adm_group_row { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.35rem 0; - border-bottom: 1px solid #eee; -} -.adm_groups_dlg .adm_group_row:last-child { - border-bottom: none; -} -.adm_groups_dlg .adm_group_row input[type=checkbox] { - margin: 0; -} -.adm_groups_dlg .adm_group_row label { - margin: 0; - font-weight: normal; -} .adm_note { margin-top: 0.75rem; diff --git a/Fuchs/wwwroot/web/fis.admin.de.js b/Fuchs/wwwroot/web/fis.admin.de.js index 4d285ec..3084ebd 100644 --- a/Fuchs/wwwroot/web/fis.admin.de.js +++ b/Fuchs/wwwroot/web/fis.admin.de.js @@ -1,5 +1,5 @@ let $adt = { - mdl: 'Administration' + mdl: 'Systemüberwachung' , sub_info: 'System' , sub_status: 'Status & Konnektivität' , sub_tests: 'Tests' @@ -81,31 +81,6 @@ let $adt = { , tm_default_subject: 'Fuchs Intranet – Test-E-Mail' , tm_default_body: 'Dies ist eine Test-E-Mail aus dem Administrationsbereich des Fuchs Intranet.' , loadfail: 'Der Systemstatus konnte nicht geladen werden.' - // user management - , sub_users: 'Benutzerverwaltung' - , u_title: 'Benutzerkonten' - , u_col_name: 'Name' - , u_col_email: 'E-Mail' - , u_col_status: 'Status' - , u_active: 'Aktiv' - , u_disabled: 'Deaktiviert' - , u_pwdreset: 'Passwort zurücksetzen' - , u_pwdreset_btn: 'Zurücksetzen' - , u_pwd_field: 'Neues Passwort' - , u_pwd_hint: 'Leer lassen, um automatisch ein Passwort zu generieren. In jedem Fall wird das neue Passwort per E-Mail an den Benutzer gesendet.' - , u_pwdreset_sent: 'Das neue Passwort wurde per E-Mail an den Benutzer gesendet.' - , u_pwdreset_noemail: 'Das Passwort wurde zurückgesetzt, konnte aber nicht per E-Mail zugestellt werden.' - , u_email: 'E-Mail-Adresse ändern' - , u_email_field: 'E-Mail-Adresse' - , u_groups: 'Gruppenzugehörigkeit' - , u_groups_title: 'Gruppen – {0}' - , u_err: { - requirements: 'Das Passwort erfüllt nicht die Mindestanforderungen (mind. 6 Zeichen).', - duplicate_email: 'Diese E-Mail-Adresse ist bereits einem anderen Benutzerkonto zugeordnet.', - reset_failed: 'Das Passwort konnte nicht zurückgesetzt werden.', - change_failed: 'Die Änderung konnte nicht gespeichert werden.' - } - , u_err_generic: 'Die Änderung konnte nicht gespeichert werden.' }; let $adm = { @@ -126,14 +101,6 @@ let $adm = { { lbl: $adt.refreshAll, id: 'adm_refresh_all', ico: 'glyphicon glyphicon-refresh', fnc: function () { $adm.init3(); } } ]); }, - /* Per-row ribbon shown while a user row is selected (mirrors the Banking module's iMn/eM swap). */ - uMn: function (rw) { - return $('#topbar').ocmsmenu([ - { lbl: $adt.u_pwdreset, fnc: function () { $adm.pwdResetDlg(rw); } }, - { lbl: $adt.u_email, fnc: function () { $adm.emailDlg(rw); } }, - { lbl: $adt.u_groups, fnc: function () { $adm.groupsDlg(rw); } } - ]); - }, load: function (frm) { $ocms.postXT({ url: $ocms.url('admin/status'), success: function (r) { @@ -160,109 +127,6 @@ let $adm = { $adm.blobCard(grid, info.blob || {}, pmap.blob); $adm.mfrCard(grid, info.mfr || {}, pmap.mfr); $adm.kvCard(grid, info.keyVault || {}, pmap.keyvault); - - $$.dc('adm_sec', frm).text($adt.sub_users); - $adm.usersCard(frm); - }, - - /* ── user management ─────────────────────────────────────────────────── */ - usersCard: function (frm) { - let card = $$.dc('adm_card adm_card_wide', frm); - let hd = $$.dc('adm_card_hd', card); - $$.s($adt.u_title).appendTo(hd); - let bd = $$.dc('adm_card_bd adm_users_bd', card); - $adm.renderUsers(bd); - }, - renderUsers: function (bd) { - bd.empty(); - let ts = $$.tblset({}, bd); - let thr = $$.tr(ts.hd); - $.each([$adt.u_col_name, $adt.u_col_email, $adt.u_col_status], function (i, lbl) { - $$.th(thr).text(lbl); - }); - $ocms.postXT({ - url: $ocms.url('admin/users'), success: function (r) { - $.each(r.users || [], function (ri, rw) { - let tr = $$.tr(ts.bdy, { id: 'adu' + rw.useraccount_id }); - $$.td(tr).text(`${rw.firstname || ''} ${rw.name || ''}`.trim()); - $$.td(tr).text(rw.email || ''); - $$.td(tr).text(bool(rw.disabled, false) === true ? $adt.u_disabled : $adt.u_active); - tr.click(function () { - tr.toggleClass('selected').siblings().rC('selected'); - if (tr.is('.selected') === true) { $adm.uMn(rw); } else { $adm.topbar(); } - }); - }); - }, error: function () { - $$.dc('adm_err', bd).text($adt.loadfail); - } - }); - }, - pwdResetDlg: function (rw) { - $ocms.dlgform([ - { name: 'useraccount_id', type: 'hidden', value: rw.useraccount_id }, - { name: 'newpassword', label: $adt.u_pwd_field, type: 'string', note: $adt.u_pwd_hint } - ], { - title: $adt.u_pwdreset, - button: $adt.u_pwdreset_btn, - submit: function (e) { - let c = $(this).ldng(1); - let qs = c.serializeObject(true, { typedvalues: true }); - $ocms.postXT({ - url: $ocms.url('admin/userpwdreset'), data: qs, success: function (response) { - alert(response.emailed === true ? $adt.u_pwdreset_sent : $adt.u_pwdreset_noemail); - c.trigger('modal_close'); - }, error: function (jqXHR) { - let code = ((jqXHR || {}).responseJSON || {}).error; - alert($adt.u_err[code] || $adt.u_err_generic); - }, complete: function () { c.ldng(0); $adm.renderUsers($('.adm_users_bd')); $adm.topbar(); } - }); - } - }); - }, - emailDlg: function (rw) { - $ocms.dlgform([ - { name: 'useraccount_id', type: 'hidden', value: rw.useraccount_id }, - { name: 'email', label: $adt.u_email_field, type: 'email', required: true, value: rw.email } - ], { - title: $adt.u_email, - submit: function (e) { - let c = $(this).ldng(1); - let qs = c.serializeObject(true, { typedvalues: true }); - $ocms.postXT({ - url: $ocms.url('admin/useremail'), data: qs, success: function () { - c.trigger('modal_close'); - }, error: function (jqXHR) { - let code = ((jqXHR || {}).responseJSON || {}).error; - alert($adt.u_err[code] || $adt.u_err_generic); - }, complete: function () { c.ldng(0); $adm.renderUsers($('.adm_users_bd')); $adm.topbar(); } - }); - } - }); - }, - groupsDlg: function (rw) { - let f = $$.dc('adm_groups_dlg'); - $ocms.postXT({ - url: $ocms.url('admin/usergroups'), data: { useraccount_id: rw.useraccount_id }, success: function (r) { - $.each(r.groups || [], function (gi, g) { - let row = $$.dc('adm_group_row', f); - let cid = 'adg' + g.usergroup_id; - let cb = $$.i({ type: 'checkbox', id: cid }).prop('checked', bool(g.member, false)).appendTo(row); - $$.lbl(g.name, { for: cid }).appendTo(row); - cb.change(function () { - let checked = cb.prop('checked'); - $ocms.postXT({ - url: $ocms.url('admin/usergroupset'), - data: { useraccount_id: rw.useraccount_id, usergroup_id: g.usergroup_id, member: checked }, - error: function () { - cb.prop('checked', !checked); - alert($adt.u_err_generic); - } - }); - }); - }); - } - }); - $ocms.dlg(f, { title: string($adt.u_groups_title, [`${rw.firstname || ''} ${rw.name || ''}`.trim()]), size: [420, 380] }); }, /* ── generic building blocks ─────────────────────────────────────────── */ diff --git a/Fuchs/wwwroot/web/fis.admin.de.min.js b/Fuchs/wwwroot/web/fis.admin.de.min.js index 81b6a86..ee63e9c 100644 --- a/Fuchs/wwwroot/web/fis.admin.de.min.js +++ b/Fuchs/wwwroot/web/fis.admin.de.min.js @@ -1 +1 @@ -let $adt={mdl:"Administration",sub_info:"System",sub_status:"Status & Konnektivität",sub_tests:"Tests",startup:"Startup-Prüfungen",startup_notrun:"Die Startup-Prüfungen sind deaktiviert oder wurden noch nicht ausgeführt (Fuchs:StartupChecks:Enabled).",startup_on:"Host",startup_at:"Ausgeführt (UTC)",chk_ok:"OK",chk_fail:"Fehler",chk_skipped:"übersprungen",chk_KeyVault:"Azure Key Vault",chk_Database:"SQL-Datenbank",chk_MFR:"MFR (ERP)",chk_PdfLicense:"Spire.PDF-Lizenz",chk_Mailer:"E-Mail (Startup-Probe)",host:"Host / Server",machine:"Hostname",os:"Betriebssystem",framework:"Framework",env:"Umgebung",testdep:"Test-Deployment",pid:"Prozess-ID",uptime:"Laufzeit",since:"Start (UTC)",hours:"Std.",db:"SQL-Datenbank",mail:"E-Mail-Dienst",blob:"Azure Blob Storage",mfr:"MFR (ERP)",kv:"Azure Key Vault",server:"Server",catalog:"Datenbank",login:"Login",mailer:"Mailer aktiv",baseurl:"Base-URL",account:"Account",serverid:"Server-ID",token:"Token hinterlegt",override:"OverrideRecipient",overrideActive:"Umleitung aktiv",enabled:"Aktiviert",configured:"Konfiguriert",invContainer:"Rechnungs-Container",remContainer:"Mahnungs-Container",host_l:"Host",creds:"Zugangsdaten hinterlegt",sync:"Sync aktiv",vault:"Vault-URI",appname:"App-Präfix",seccount:"Verwaltete Secrets",clientreg:"Client registriert",duration:"Dauer",checked:"Geprüft",detail:"Detail",st_ok:"OK",st_error:"Fehler",st_disabled:"Deaktiviert",st_unconfigured:"Nicht konfiguriert",st_checking:"Prüfe…",refresh:"Aktualisieren",refreshAll:"Alle prüfen",yes:"Ja",no:"Nein",testmail:"Test-E-Mail",testmail_btn:"Test-E-Mail senden",tm_to:"Empfänger",tm_subject:"Betreff",tm_body:"Nachricht",tm_hint:"Der Versand nutzt die normale E-Mail-Pipeline; bei gesetztem OverrideRecipient wird die Nachricht dorthin umgeleitet.",tm_sent:"Test-E-Mail übergeben.",tm_failed:"Test-E-Mail konnte nicht gesendet werden.",tm_default_subject:"Fuchs Intranet – Test-E-Mail",tm_default_body:"Dies ist eine Test-E-Mail aus dem Administrationsbereich des Fuchs Intranet.",loadfail:"Der Systemstatus konnte nicht geladen werden.",sub_users:"Benutzerverwaltung",u_title:"Benutzerkonten",u_col_name:"Name",u_col_email:"E-Mail",u_col_status:"Status",u_active:"Aktiv",u_disabled:"Deaktiviert",u_pwdreset:"Passwort zurücksetzen",u_pwdreset_btn:"Zurücksetzen",u_pwd_field:"Neues Passwort",u_pwd_hint:"Leer lassen, um automatisch ein Passwort zu generieren. In jedem Fall wird das neue Passwort per E-Mail an den Benutzer gesendet.",u_pwdreset_sent:"Das neue Passwort wurde per E-Mail an den Benutzer gesendet.",u_pwdreset_noemail:"Das Passwort wurde zurückgesetzt, konnte aber nicht per E-Mail zugestellt werden.",u_email:"E-Mail-Adresse ändern",u_email_field:"E-Mail-Adresse",u_groups:"Gruppenzugehörigkeit",u_groups_title:"Gruppen – {0}",u_err:{requirements:"Das Passwort erfüllt nicht die Mindestanforderungen (mind. 6 Zeichen).",duplicate_email:"Diese E-Mail-Adresse ist bereits einem anderen Benutzerkonto zugeordnet.",reset_failed:"Das Passwort konnte nicht zurückgesetzt werden.",change_failed:"Die Änderung konnte nicht gespeichert werden."},u_err_generic:"Die Änderung konnte nicht gespeichert werden."},$adm={init2:function(){$ocms.getScript([],(function(){$adm.init3()}))},init3:function(){$fis.cf(!0),$fis.lf(!0),$("#activemodule").text($adt.mdl),$adm.topbar();let e=$$.dc("admfrm",$("#contentframe")).ldng(1);$adm.load(e)},topbar:function(){return $("#topbar").ocmsmenu([{lbl:$adt.refreshAll,id:"adm_refresh_all",ico:"glyphicon glyphicon-refresh",fnc:function(){$adm.init3()}}])},uMn:function(e){return $("#topbar").ocmsmenu([{lbl:$adt.u_pwdreset,fnc:function(){$adm.pwdResetDlg(e)}},{lbl:$adt.u_email,fnc:function(){$adm.emailDlg(e)}},{lbl:$adt.u_groups,fnc:function(){$adm.groupsDlg(e)}}])},load:function(e){$ocms.postXT({url:$ocms.url("admin/status"),success:function(t){e.empty(),$adm.render(e,t.info||{},t.probes||[])},error:function(){e.empty(),$$.dc("adm_err",e).text($adt.loadfail)},complete:function(){e.ldng(0)}})},render:function(e,t,a){let d={};$.each(a,(function(e,t){d[t.component]=t})),$$.dc("adm_sec",e).text($adt.sub_info),$adm.sysCard(e,t),$adm.startupCard(e,t.startupChecks),$$.dc("adm_sec",e).text($adt.sub_status);let r=$$.dc("adm_grid",e);$adm.dbCard(r,t.database||{},d.database),$adm.mailCard(r,t.email||{}),$adm.blobCard(r,t.blob||{},d.blob),$adm.mfrCard(r,t.mfr||{},d.mfr),$adm.kvCard(r,t.keyVault||{},d.keyvault),$$.dc("adm_sec",e).text($adt.sub_users),$adm.usersCard(e)},usersCard:function(e){let t=$$.dc("adm_card adm_card_wide",e),a=$$.dc("adm_card_hd",t);$$.s($adt.u_title).appendTo(a);let d=$$.dc("adm_card_bd adm_users_bd",t);$adm.renderUsers(d)},renderUsers:function(e){e.empty();let t=$$.tblset({},e),a=$$.tr(t.hd);$.each([$adt.u_col_name,$adt.u_col_email,$adt.u_col_status],(function(e,t){$$.th(a).text(t)})),$ocms.postXT({url:$ocms.url("admin/users"),success:function(e){$.each(e.users||[],(function(e,a){let d=$$.tr(t.bdy,{id:"adu"+a.useraccount_id});$$.td(d).text(`${a.firstname||""} ${a.name||""}`.trim()),$$.td(d).text(a.email||""),$$.td(d).text(!0===bool(a.disabled,!1)?$adt.u_disabled:$adt.u_active),d.click((function(){d.toggleClass("selected").siblings().rC("selected"),!0===d.is(".selected")?$adm.uMn(a):$adm.topbar()}))}))},error:function(){$$.dc("adm_err",e).text($adt.loadfail)}})},pwdResetDlg:function(e){$ocms.dlgform([{name:"useraccount_id",type:"hidden",value:e.useraccount_id},{name:"newpassword",label:$adt.u_pwd_field,type:"string",note:$adt.u_pwd_hint}],{title:$adt.u_pwdreset,button:$adt.u_pwdreset_btn,submit:function(e){let t=$(this).ldng(1),a=t.serializeObject(!0,{typedvalues:!0});$ocms.postXT({url:$ocms.url("admin/userpwdreset"),data:a,success:function(e){alert(!0===e.emailed?$adt.u_pwdreset_sent:$adt.u_pwdreset_noemail),t.trigger("modal_close")},error:function(e){let t=((e||{}).responseJSON||{}).error;alert($adt.u_err[t]||$adt.u_err_generic)},complete:function(){t.ldng(0),$adm.renderUsers($(".adm_users_bd")),$adm.topbar()}})}})},emailDlg:function(e){$ocms.dlgform([{name:"useraccount_id",type:"hidden",value:e.useraccount_id},{name:"email",label:$adt.u_email_field,type:"email",required:!0,value:e.email}],{title:$adt.u_email,submit:function(e){let t=$(this).ldng(1),a=t.serializeObject(!0,{typedvalues:!0});$ocms.postXT({url:$ocms.url("admin/useremail"),data:a,success:function(){t.trigger("modal_close")},error:function(e){let t=((e||{}).responseJSON||{}).error;alert($adt.u_err[t]||$adt.u_err_generic)},complete:function(){t.ldng(0),$adm.renderUsers($(".adm_users_bd")),$adm.topbar()}})}})},groupsDlg:function(e){let t=$$.dc("adm_groups_dlg");$ocms.postXT({url:$ocms.url("admin/usergroups"),data:{useraccount_id:e.useraccount_id},success:function(a){$.each(a.groups||[],(function(a,d){let r=$$.dc("adm_group_row",t),n="adg"+d.usergroup_id,s=$$.i({type:"checkbox",id:n}).prop("checked",bool(d.member,!1)).appendTo(r);$$.lbl(d.name,{for:n}).appendTo(r),s.change((function(){let t=s.prop("checked");$ocms.postXT({url:$ocms.url("admin/usergroupset"),data:{useraccount_id:e.useraccount_id,usergroup_id:d.usergroup_id,member:t},error:function(){s.prop("checked",!t),alert($adt.u_err_generic)}})}))}))}}),$ocms.dlg(t,{title:string($adt.u_groups_title,[`${e.firstname||""} ${e.name||""}`.trim()]),size:[420,380]})},kvTable:function(e,t){let a=$$.tbl().addClass("adm_kv");return $.each(t||[],(function(e,t){if(!t)return!0;let d=$$.tr(a);$$.td(d).addClass("k").text(null==t[0]?"":String(t[0]));let r=$$.td(d).addClass("v");t[1]instanceof jQuery?r.append(t[1]):r.text(null==t[1]?"":String(t[1]))})),e instanceof jQuery&&e.append(a),a},yesNo:function(e){return $$.sc("adm_bool adm_bool_"+(e?"y":"n"),e?$adt.yes:$adt.no)},setPill:function(e,t){let a=(t=t||{}).status||"unconfigured",d={ok:$adt.st_ok,error:$adt.st_error,disabled:$adt.st_disabled,unconfigured:$adt.st_unconfigured,checking:$adt.st_checking}[a]||a;return e.attr("class","adm_pill adm_pill_"+a).text(d),e},probeCard:function(e,t,a,d,r){let n=$$.dc("adm_card",e),s=$$.dc("adm_card_hd",n);$$.s(t).appendTo(s);let i=$$.sc("adm_pill").appendTo(s),o=$$.bbtn($adt.refresh,"adm_btn").appendTo(s),c=$$.dc("adm_card_bd",n);$adm.kvTable(c,d);let l=$$.dc("adm_probe",c),u=$$.dc("adm_probe_msg",l),m=$$.dc("adm_probe_metrics",l),_=$$.dc("adm_probe_det",l),p=$$.dc("adm_probe_dur",l),f=function(e){e=e||{},$adm.setPill(i,e),u.text(e.message||""),m.empty(),$.each(e.metrics||[],(function(e,t){let a=$$.dc("adm_metric",m);$$.sc("adm_metric_k").text(t.label||"").appendTo(a),$$.sc("adm_metric_v").text(null==t.value?"":String(t.value)).appendTo(a)})),_.text(e.detail||""),p.text(null!=e.durationMs?e.durationMs+" ms":"")};return f(r),o.click((function(){$adm.setPill(i,{status:"checking"}),u.text(""),m.empty(),_.text(""),p.text(""),$ocms.postXT({url:$ocms.url("admin/probe/"+a),success:function(e){f(e.probe)},error:function(){$adm.setPill(i,{status:"error"}),u.text($adt.st_error)}})})),n},sysCard:function(e,t){let a=$$.dc("adm_card adm_card_wide",e),d=$$.dc("adm_card_hd",a);$$.s($adt.host).appendTo(d);let r=$$.sc("adm_env").text(t.environment||"");!0===t.isTestDeployment&&r.aC("adm_env_test"),r.appendTo(d);let n=$$.dc("adm_card_bd",a);$adm.kvTable(n,[[$adt.machine,t.machineName],[$adt.os,t.osDescription],[$adt.framework,t.frameworkDescription],[$adt.env,t.environment],[$adt.testdep,$adm.yesNo(!0===t.isTestDeployment)],[$adt.pid,t.processId],[$adt.since,t.processStartUtc?fdt(t.processStartUtc):""],[$adt.uptime,null!=t.uptimeHours?t.uptimeHours+" "+$adt.hours:""]])},startupCard:function(e,t){let a=$$.dc("adm_card adm_card_wide",e),d=$$.dc("adm_card_hd",a);$$.s($adt.startup).appendTo(d);let r=$$.dc("adm_card_bd",a);if(!t||!0!==t.ran)return $adm.setPill($$.sc("adm_pill").appendTo(d),{status:"disabled"}),void $$.dc("adm_note",r).text($adt.startup_notrun);let n=t.items||[],s=!1;$.each(n,(function(e,t){!0===t.enabled&&!0!==t.ok&&(s=!0)})),$adm.setPill($$.sc("adm_pill").appendTo(d),{status:s?"error":"ok"}),$adm.kvTable(r,[[$adt.startup_on,t.machineName],[$adt.startup_at,t.completedUtc?fdt(t.completedUtc):""]]);let i=$$.dc("adm_checks",r);$.each(n,(function(e,t){let a=$$.dc("adm_check",i);$$.sc("adm_check_k").text($adt["chk_"+t.name]||t.name).appendTo(a);let d=!0!==t.enabled?"skipped":!0===t.ok?"ok":"fail",r={ok:$adt.chk_ok,fail:$adt.chk_fail,skipped:$adt.chk_skipped}[d];$$.sc("adm_check_v adm_check_"+d).text(r).appendTo(a)}))},dbCard:function(e,t,a){$adm.probeCard(e,$adt.db,"database",[[$adt.configured,$adm.yesNo(!0===t.configured)],[$adt.server,t.server],[$adt.catalog,t.catalog],[$adt.login,t.userId]],a)},mailCard:function(e,t){let a=$$.dc("adm_card",e),d=$$.dc("adm_card_hd",a);$$.s($adt.mail).appendTo(d),$adm.setPill($$.sc("adm_pill").appendTo(d),{status:!0===t.mailerEnabled?"ok":"disabled"});let r=$$.dc("adm_card_bd",a),n=[[$adt.mailer,$adm.yesNo(!0===t.mailerEnabled)],[$adt.baseurl,t.baseUrl],[$adt.account,t.accountId],[$adt.serverid,t.serverId],[$adt.token,$adm.yesNo(!0===t.tokenConfigured)],[$adt.overrideActive,$adm.yesNo(!0===t.overrideActive)]];!0===t.overrideActive&&n.push([$adt.override,t.overrideRecipient]),$adm.kvTable(r,n);let s=$$.dc("adm_card_ft",a);$$.bbtn($adt.testmail_btn,"adm_btn adm_btn_primary").appendTo(s).click($adm.testMailDlg)},blobCard:function(e,t,a){$adm.probeCard(e,$adt.blob,"blob",[[$adt.enabled,$adm.yesNo(!0===t.enabled)],[$adt.configured,$adm.yesNo(!0===t.configured)],[$adt.invContainer,t.invoiceContainer],[$adt.remContainer,t.reminderContainer]],a)},mfrCard:function(e,t,a){$adm.probeCard(e,$adt.mfr,"mfr",[[$adt.host_l,t.host],[$adt.creds,$adm.yesNo(!0===t.credentialsConfigured)],[$adt.sync,$adm.yesNo(!0===t.syncEnabled)]],a)},kvCard:function(e,t,a){$adm.probeCard(e,$adt.kv,"keyvault",[[$adt.vault,t.vaultUri],[$adt.appname,t.appName],[$adt.seccount,t.managedSecretCount],[$adt.clientreg,$adm.yesNo(!0===t.clientRegistered)]],a)},testMailDlg:function(){$ocms.dlgform([{name:"to",label:$adt.tm_to,type:"email",required:!0,value:$ocms.auth.email||""},{name:"subject",label:$adt.tm_subject,type:"string",required:!0,value:$adt.tm_default_subject},{name:"body",label:$adt.tm_body,type:"text",required:!0,value:$adt.tm_default_body}],{title:$adt.testmail,button:$adt.testmail_btn,size:[420,560],addcontent:$$.dc("adm_note").text($adt.tm_hint),url:$ocms.url("admin/testmail"),typedvalues:!0,success:function(e){let t=e&&e.result||{};alert(t.message||(!0===t.sent?$adt.tm_sent:$adt.tm_failed))}})}},$$adm={init2:$adm.init2,auth:{}};export default $$adm; \ No newline at end of file +let $adt={mdl:"Systemüberwachung",sub_info:"System",sub_status:"Status & Konnektivität",sub_tests:"Tests",startup:"Startup-Prüfungen",startup_notrun:"Die Startup-Prüfungen sind deaktiviert oder wurden noch nicht ausgeführt (Fuchs:StartupChecks:Enabled).",startup_on:"Host",startup_at:"Ausgeführt (UTC)",chk_ok:"OK",chk_fail:"Fehler",chk_skipped:"übersprungen",chk_KeyVault:"Azure Key Vault",chk_Database:"SQL-Datenbank",chk_MFR:"MFR (ERP)",chk_PdfLicense:"Spire.PDF-Lizenz",chk_Mailer:"E-Mail (Startup-Probe)",host:"Host / Server",machine:"Hostname",os:"Betriebssystem",framework:"Framework",env:"Umgebung",testdep:"Test-Deployment",pid:"Prozess-ID",uptime:"Laufzeit",since:"Start (UTC)",hours:"Std.",db:"SQL-Datenbank",mail:"E-Mail-Dienst",blob:"Azure Blob Storage",mfr:"MFR (ERP)",kv:"Azure Key Vault",server:"Server",catalog:"Datenbank",login:"Login",mailer:"Mailer aktiv",baseurl:"Base-URL",account:"Account",serverid:"Server-ID",token:"Token hinterlegt",override:"OverrideRecipient",overrideActive:"Umleitung aktiv",enabled:"Aktiviert",configured:"Konfiguriert",invContainer:"Rechnungs-Container",remContainer:"Mahnungs-Container",host_l:"Host",creds:"Zugangsdaten hinterlegt",sync:"Sync aktiv",vault:"Vault-URI",appname:"App-Präfix",seccount:"Verwaltete Secrets",clientreg:"Client registriert",duration:"Dauer",checked:"Geprüft",detail:"Detail",st_ok:"OK",st_error:"Fehler",st_disabled:"Deaktiviert",st_unconfigured:"Nicht konfiguriert",st_checking:"Prüfe…",refresh:"Aktualisieren",refreshAll:"Alle prüfen",yes:"Ja",no:"Nein",testmail:"Test-E-Mail",testmail_btn:"Test-E-Mail senden",tm_to:"Empfänger",tm_subject:"Betreff",tm_body:"Nachricht",tm_hint:"Der Versand nutzt die normale E-Mail-Pipeline; bei gesetztem OverrideRecipient wird die Nachricht dorthin umgeleitet.",tm_sent:"Test-E-Mail übergeben.",tm_failed:"Test-E-Mail konnte nicht gesendet werden.",tm_default_subject:"Fuchs Intranet – Test-E-Mail",tm_default_body:"Dies ist eine Test-E-Mail aus dem Administrationsbereich des Fuchs Intranet.",loadfail:"Der Systemstatus konnte nicht geladen werden."},$adm={init2:function(){$ocms.getScript([],(function(){$adm.init3()}))},init3:function(){$fis.cf(!0),$fis.lf(!0),$("#activemodule").text($adt.mdl),$adm.topbar();let e=$$.dc("admfrm",$("#contentframe")).ldng(1);$adm.load(e)},topbar:function(){return $("#topbar").ocmsmenu([{lbl:$adt.refreshAll,id:"adm_refresh_all",ico:"glyphicon glyphicon-refresh",fnc:function(){$adm.init3()}}])},load:function(e){$ocms.postXT({url:$ocms.url("admin/status"),success:function(t){e.empty(),$adm.render(e,t.info||{},t.probes||[])},error:function(){e.empty(),$$.dc("adm_err",e).text($adt.loadfail)},complete:function(){e.ldng(0)}})},render:function(e,t,a){let d={};$.each(a,(function(e,t){d[t.component]=t})),$$.dc("adm_sec",e).text($adt.sub_info),$adm.sysCard(e,t),$adm.startupCard(e,t.startupChecks),$$.dc("adm_sec",e).text($adt.sub_status);let r=$$.dc("adm_grid",e);$adm.dbCard(r,t.database||{},d.database),$adm.mailCard(r,t.email||{}),$adm.blobCard(r,t.blob||{},d.blob),$adm.mfrCard(r,t.mfr||{},d.mfr),$adm.kvCard(r,t.keyVault||{},d.keyvault)},kvTable:function(e,t){let a=$$.tbl().addClass("adm_kv");return $.each(t||[],(function(e,t){if(!t)return!0;let d=$$.tr(a);$$.td(d).addClass("k").text(null==t[0]?"":String(t[0]));let r=$$.td(d).addClass("v");t[1]instanceof jQuery?r.append(t[1]):r.text(null==t[1]?"":String(t[1]))})),e instanceof jQuery&&e.append(a),a},yesNo:function(e){return $$.sc("adm_bool adm_bool_"+(e?"y":"n"),e?$adt.yes:$adt.no)},setPill:function(e,t){let a=(t=t||{}).status||"unconfigured",d={ok:$adt.st_ok,error:$adt.st_error,disabled:$adt.st_disabled,unconfigured:$adt.st_unconfigured,checking:$adt.st_checking}[a]||a;return e.attr("class","adm_pill adm_pill_"+a).text(d),e},probeCard:function(e,t,a,d,r){let n=$$.dc("adm_card",e),i=$$.dc("adm_card_hd",n);$$.s(t).appendTo(i);let s=$$.sc("adm_pill").appendTo(i),c=$$.bbtn($adt.refresh,"adm_btn").appendTo(i),o=$$.dc("adm_card_bd",n);$adm.kvTable(o,d);let l=$$.dc("adm_probe",o),m=$$.dc("adm_probe_msg",l),u=$$.dc("adm_probe_metrics",l),_=$$.dc("adm_probe_det",l),p=$$.dc("adm_probe_dur",l),b=function(e){e=e||{},$adm.setPill(s,e),m.text(e.message||""),u.empty(),$.each(e.metrics||[],(function(e,t){let a=$$.dc("adm_metric",u);$$.sc("adm_metric_k").text(t.label||"").appendTo(a),$$.sc("adm_metric_v").text(null==t.value?"":String(t.value)).appendTo(a)})),_.text(e.detail||""),p.text(null!=e.durationMs?e.durationMs+" ms":"")};return b(r),c.click((function(){$adm.setPill(s,{status:"checking"}),m.text(""),u.empty(),_.text(""),p.text(""),$ocms.postXT({url:$ocms.url("admin/probe/"+a),success:function(e){b(e.probe)},error:function(){$adm.setPill(s,{status:"error"}),m.text($adt.st_error)}})})),n},sysCard:function(e,t){let a=$$.dc("adm_card adm_card_wide",e),d=$$.dc("adm_card_hd",a);$$.s($adt.host).appendTo(d);let r=$$.sc("adm_env").text(t.environment||"");!0===t.isTestDeployment&&r.aC("adm_env_test"),r.appendTo(d);let n=$$.dc("adm_card_bd",a);$adm.kvTable(n,[[$adt.machine,t.machineName],[$adt.os,t.osDescription],[$adt.framework,t.frameworkDescription],[$adt.env,t.environment],[$adt.testdep,$adm.yesNo(!0===t.isTestDeployment)],[$adt.pid,t.processId],[$adt.since,t.processStartUtc?fdt(t.processStartUtc):""],[$adt.uptime,null!=t.uptimeHours?t.uptimeHours+" "+$adt.hours:""]])},startupCard:function(e,t){let a=$$.dc("adm_card adm_card_wide",e),d=$$.dc("adm_card_hd",a);$$.s($adt.startup).appendTo(d);let r=$$.dc("adm_card_bd",a);if(!t||!0!==t.ran)return $adm.setPill($$.sc("adm_pill").appendTo(d),{status:"disabled"}),void $$.dc("adm_note",r).text($adt.startup_notrun);let n=t.items||[],i=!1;$.each(n,(function(e,t){!0===t.enabled&&!0!==t.ok&&(i=!0)})),$adm.setPill($$.sc("adm_pill").appendTo(d),{status:i?"error":"ok"}),$adm.kvTable(r,[[$adt.startup_on,t.machineName],[$adt.startup_at,t.completedUtc?fdt(t.completedUtc):""]]);let s=$$.dc("adm_checks",r);$.each(n,(function(e,t){let a=$$.dc("adm_check",s);$$.sc("adm_check_k").text($adt["chk_"+t.name]||t.name).appendTo(a);let d=!0!==t.enabled?"skipped":!0===t.ok?"ok":"fail",r={ok:$adt.chk_ok,fail:$adt.chk_fail,skipped:$adt.chk_skipped}[d];$$.sc("adm_check_v adm_check_"+d).text(r).appendTo(a)}))},dbCard:function(e,t,a){$adm.probeCard(e,$adt.db,"database",[[$adt.configured,$adm.yesNo(!0===t.configured)],[$adt.server,t.server],[$adt.catalog,t.catalog],[$adt.login,t.userId]],a)},mailCard:function(e,t){let a=$$.dc("adm_card",e),d=$$.dc("adm_card_hd",a);$$.s($adt.mail).appendTo(d),$adm.setPill($$.sc("adm_pill").appendTo(d),{status:!0===t.mailerEnabled?"ok":"disabled"});let r=$$.dc("adm_card_bd",a),n=[[$adt.mailer,$adm.yesNo(!0===t.mailerEnabled)],[$adt.baseurl,t.baseUrl],[$adt.account,t.accountId],[$adt.serverid,t.serverId],[$adt.token,$adm.yesNo(!0===t.tokenConfigured)],[$adt.overrideActive,$adm.yesNo(!0===t.overrideActive)]];!0===t.overrideActive&&n.push([$adt.override,t.overrideRecipient]),$adm.kvTable(r,n);let i=$$.dc("adm_card_ft",a);$$.bbtn($adt.testmail_btn,"adm_btn adm_btn_primary").appendTo(i).click($adm.testMailDlg)},blobCard:function(e,t,a){$adm.probeCard(e,$adt.blob,"blob",[[$adt.enabled,$adm.yesNo(!0===t.enabled)],[$adt.configured,$adm.yesNo(!0===t.configured)],[$adt.invContainer,t.invoiceContainer],[$adt.remContainer,t.reminderContainer]],a)},mfrCard:function(e,t,a){$adm.probeCard(e,$adt.mfr,"mfr",[[$adt.host_l,t.host],[$adt.creds,$adm.yesNo(!0===t.credentialsConfigured)],[$adt.sync,$adm.yesNo(!0===t.syncEnabled)]],a)},kvCard:function(e,t,a){$adm.probeCard(e,$adt.kv,"keyvault",[[$adt.vault,t.vaultUri],[$adt.appname,t.appName],[$adt.seccount,t.managedSecretCount],[$adt.clientreg,$adm.yesNo(!0===t.clientRegistered)]],a)},testMailDlg:function(){$ocms.dlgform([{name:"to",label:$adt.tm_to,type:"email",required:!0,value:$ocms.auth.email||""},{name:"subject",label:$adt.tm_subject,type:"string",required:!0,value:$adt.tm_default_subject},{name:"body",label:$adt.tm_body,type:"text",required:!0,value:$adt.tm_default_body}],{title:$adt.testmail,button:$adt.testmail_btn,size:[420,560],addcontent:$$.dc("adm_note").text($adt.tm_hint),url:$ocms.url("admin/testmail"),typedvalues:!0,success:function(e){let t=e&&e.result||{};alert(t.message||(!0===t.sent?$adt.tm_sent:$adt.tm_failed))}})}},$$adm={init2:$adm.init2,auth:{}};export default $$adm; \ No newline at end of file diff --git a/Fuchs/wwwroot/web/fis.admin.min.css b/Fuchs/wwwroot/web/fis.admin.min.css index 59c8c88..ba7f0c9 100644 --- a/Fuchs/wwwroot/web/fis.admin.min.css +++ b/Fuchs/wwwroot/web/fis.admin.min.css @@ -1 +1 @@ -.admfrm{padding:1.5rem}.admfrm .adm_sec{font-size:1.2rem;font-weight:700;color:#1b4379;margin:1.5rem 0 .75rem;border-bottom:2px solid #1b4379;padding-bottom:.25rem}.admfrm .adm_sec:first-child{margin-top:0}.admfrm .adm_err{padding:1rem;color:#b00020;font-weight:700}.admfrm .adm_grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(22rem,1fr));gap:1rem;align-items:start}.admfrm .adm_card{border:1px solid #d5d5d5;border-radius:.4rem;background:#fff;box-shadow:0 1px 3px rgba(50,50,50,.12);overflow:hidden}.admfrm .adm_card.adm_card_wide{grid-column:1/-1}.admfrm .adm_card_hd{display:flex;align-items:center;gap:.5rem;padding:.6rem .85rem;background:#1b4379;color:#fff;font-weight:700}.admfrm .adm_card_hd>span:first-child{flex:1 1 auto}.admfrm .adm_card_bd{padding:.6rem .85rem}.admfrm .adm_card_ft{padding:.5rem .85rem .75rem;text-align:right}.admfrm table.adm_kv{border-collapse:collapse;width:100%}.admfrm table.adm_kv td{padding:.25rem .35rem;border-bottom:1px solid #eee;vertical-align:top;font-size:.92rem}.admfrm table.adm_kv td.k{color:#666;white-space:nowrap;width:40%}.admfrm table.adm_kv td.v{word-break:break-word}.admfrm table.adm_kv tr:last-child td{border-bottom:none}.admfrm .adm_probe{margin-top:.6rem;padding-top:.5rem;border-top:1px dashed #ddd;font-size:.88rem}.admfrm .adm_probe .adm_probe_msg{font-weight:600}.admfrm .adm_probe .adm_probe_det{color:#555;word-break:break-word;margin-top:.15rem}.admfrm .adm_probe .adm_probe_dur{color:#999;margin-top:.15rem;font-size:.8rem}.admfrm .adm_probe .adm_probe_metrics{margin-top:.35rem}.admfrm .adm_probe .adm_probe_metrics .adm_metric{display:flex;justify-content:space-between;gap:.5rem;padding:.1rem 0;border-bottom:1px dotted #eee}.admfrm .adm_probe .adm_probe_metrics .adm_metric:last-child{border-bottom:none}.admfrm .adm_probe .adm_probe_metrics .adm_metric .adm_metric_k{color:#555;word-break:break-word}.admfrm .adm_probe .adm_probe_metrics .adm_metric .adm_metric_v{font-weight:600;white-space:nowrap}.admfrm .adm_checks{margin-top:.5rem}.admfrm .adm_checks .adm_check{display:flex;justify-content:space-between;align-items:center;gap:.5rem;padding:.25rem 0;border-bottom:1px solid #eee}.admfrm .adm_checks .adm_check:last-child{border-bottom:none}.admfrm .adm_checks .adm_check .adm_check_k{color:#333}.admfrm .adm_checks .adm_check .adm_check_v{font-weight:700;font-size:.8rem;padding:.05rem .5rem;border-radius:.25rem;white-space:nowrap}.admfrm .adm_checks .adm_check .adm_check_ok{background:#e2f3da;color:#2f6d18}.admfrm .adm_checks .adm_check .adm_check_fail{background:#f3ded9;color:#c0341d}.admfrm .adm_checks .adm_check .adm_check_skipped{background:#ececec;color:#777}.admfrm .adm_pill{display:inline-block;padding:.1rem .55rem;border-radius:1rem;font-size:.78rem;font-weight:700;color:#fff;background:#999;white-space:nowrap}.admfrm .adm_pill_ok{background:#56a532}.admfrm .adm_pill_error{background:#c0341d}.admfrm .adm_pill_disabled{background:#888}.admfrm .adm_pill_unconfigured{background:#d69100}.admfrm .adm_pill_checking{background:#2a72c4}.admfrm .adm_bool{display:inline-block;padding:0 .4rem;border-radius:.25rem;font-size:.8rem;font-weight:700}.admfrm .adm_bool_y{background:#e2f3da;color:#2f6d18}.admfrm .adm_bool_n{background:#f3e0dd;color:#9c2a17}.admfrm .adm_env{font-size:.8rem;font-weight:400;padding:.1rem .5rem;border-radius:.25rem;background:rgba(255,255,255,.25)}.admfrm .adm_env.adm_env_test{background:#13a143}.admfrm .adm_btn{cursor:pointer;border-radius:.28rem;padding:.2rem .6rem;font-size:.85rem;border:1px solid #ababab;background-color:#fff;color:#333}.admfrm .adm_btn.adm_btn_primary{background-color:#56a532;border-color:rgb(69.68,133.688372093,40.511627907);color:#fff}.admfrm .adm_card_hd .adm_btn{background-color:rgba(255,255,255,.15);color:#fff;border-color:rgba(255,255,255,.4)}.admfrm .adm_users_bd table tbody tr{cursor:pointer}.admfrm .adm_users_bd table tbody tr:hover td{background-color:#f3f6fb}.adm_groups_dlg{padding:.25rem 0}.adm_groups_dlg .adm_group_row{display:flex;align-items:center;gap:.5rem;padding:.35rem 0;border-bottom:1px solid #eee}.adm_groups_dlg .adm_group_row:last-child{border-bottom:none}.adm_groups_dlg .adm_group_row input[type=checkbox]{margin:0}.adm_groups_dlg .adm_group_row label{margin:0;font-weight:400}.adm_note{margin-top:.75rem;padding:.5rem .6rem;background:#f3f6fb;border-left:3px solid #1b4379;font-size:.85rem;color:#444} \ No newline at end of file +.admfrm{padding:1.5rem}.admfrm .adm_sec{font-size:1.2rem;font-weight:700;color:#1b4379;margin:1.5rem 0 .75rem;border-bottom:2px solid #1b4379;padding-bottom:.25rem}.admfrm .adm_sec:first-child{margin-top:0}.admfrm .adm_err{padding:1rem;color:#b00020;font-weight:700}.admfrm .adm_grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(22rem,1fr));gap:1rem;align-items:start}.admfrm .adm_card{border:1px solid #d5d5d5;border-radius:.4rem;background:#fff;box-shadow:0 1px 3px rgba(50,50,50,.12);overflow:hidden}.admfrm .adm_card.adm_card_wide{grid-column:1/-1}.admfrm .adm_card_hd{display:flex;align-items:center;gap:.5rem;padding:.6rem .85rem;background:#1b4379;color:#fff;font-weight:700}.admfrm .adm_card_hd>span:first-child{flex:1 1 auto}.admfrm .adm_card_bd{padding:.6rem .85rem}.admfrm .adm_card_ft{padding:.5rem .85rem .75rem;text-align:right}.admfrm table.adm_kv{border-collapse:collapse;width:100%}.admfrm table.adm_kv td{padding:.25rem .35rem;border-bottom:1px solid #eee;vertical-align:top;font-size:.92rem}.admfrm table.adm_kv td.k{color:#666;white-space:nowrap;width:40%}.admfrm table.adm_kv td.v{word-break:break-word}.admfrm table.adm_kv tr:last-child td{border-bottom:none}.admfrm .adm_probe{margin-top:.6rem;padding-top:.5rem;border-top:1px dashed #ddd;font-size:.88rem}.admfrm .adm_probe .adm_probe_msg{font-weight:600}.admfrm .adm_probe .adm_probe_det{color:#555;word-break:break-word;margin-top:.15rem}.admfrm .adm_probe .adm_probe_dur{color:#999;margin-top:.15rem;font-size:.8rem}.admfrm .adm_probe .adm_probe_metrics{margin-top:.35rem}.admfrm .adm_probe .adm_probe_metrics .adm_metric{display:flex;justify-content:space-between;gap:.5rem;padding:.1rem 0;border-bottom:1px dotted #eee}.admfrm .adm_probe .adm_probe_metrics .adm_metric:last-child{border-bottom:none}.admfrm .adm_probe .adm_probe_metrics .adm_metric .adm_metric_k{color:#555;word-break:break-word}.admfrm .adm_probe .adm_probe_metrics .adm_metric .adm_metric_v{font-weight:600;white-space:nowrap}.admfrm .adm_checks{margin-top:.5rem}.admfrm .adm_checks .adm_check{display:flex;justify-content:space-between;align-items:center;gap:.5rem;padding:.25rem 0;border-bottom:1px solid #eee}.admfrm .adm_checks .adm_check:last-child{border-bottom:none}.admfrm .adm_checks .adm_check .adm_check_k{color:#333}.admfrm .adm_checks .adm_check .adm_check_v{font-weight:700;font-size:.8rem;padding:.05rem .5rem;border-radius:.25rem;white-space:nowrap}.admfrm .adm_checks .adm_check .adm_check_ok{background:#e2f3da;color:#2f6d18}.admfrm .adm_checks .adm_check .adm_check_fail{background:#f3ded9;color:#c0341d}.admfrm .adm_checks .adm_check .adm_check_skipped{background:#ececec;color:#777}.admfrm .adm_pill{display:inline-block;padding:.1rem .55rem;border-radius:1rem;font-size:.78rem;font-weight:700;color:#fff;background:#999;white-space:nowrap}.admfrm .adm_pill_ok{background:#56a532}.admfrm .adm_pill_error{background:#c0341d}.admfrm .adm_pill_disabled{background:#888}.admfrm .adm_pill_unconfigured{background:#d69100}.admfrm .adm_pill_checking{background:#2a72c4}.admfrm .adm_bool{display:inline-block;padding:0 .4rem;border-radius:.25rem;font-size:.8rem;font-weight:700}.admfrm .adm_bool_y{background:#e2f3da;color:#2f6d18}.admfrm .adm_bool_n{background:#f3e0dd;color:#9c2a17}.admfrm .adm_env{font-size:.8rem;font-weight:400;padding:.1rem .5rem;border-radius:.25rem;background:rgba(255,255,255,.25)}.admfrm .adm_env.adm_env_test{background:#13a143}.admfrm .adm_btn{cursor:pointer;border-radius:.28rem;padding:.2rem .6rem;font-size:.85rem;border:1px solid #ababab;background-color:#fff;color:#333}.admfrm .adm_btn.adm_btn_primary{background-color:#56a532;border-color:rgb(69.68,133.688372093,40.511627907);color:#fff}.admfrm .adm_card_hd .adm_btn{background-color:rgba(255,255,255,.15);color:#fff;border-color:rgba(255,255,255,.4)}.adm_note{margin-top:.75rem;padding:.5rem .6rem;background:#f3f6fb;border-left:3px solid #1b4379;font-size:.85rem;color:#444} \ No newline at end of file diff --git a/Fuchs/wwwroot/web/fis.js b/Fuchs/wwwroot/web/fis.js index c2802c0..787d162 100644 --- a/Fuchs/wwwroot/web/fis.js +++ b/Fuchs/wwwroot/web/fis.js @@ -2843,6 +2843,8 @@ $.extend($t, { , m_todo: 'ToDos' , m_bcd: 'BankBuchungen' , m_admin: 'Administration' + , m_admin_status: 'Systemüberwachung' + , m_usr: 'Benutzerverwaltung' , rsp: 'Passwort ändern' , pnm: 'Die Passwörter stimmen nicht überein' , cps: 'Das neue Passwort wurde gespeichert.' @@ -3223,15 +3225,23 @@ $fis.draft = { ]); })(); -/* The Administration module is restricted to users whose fds_sys authorization exceeds 4. - The button is added — and only then does clicking it load the module script — after the - asynchronous auth check resolves, so lower-privileged users never see it or fetch the code. +/* "Administration" is a single sidemenu dropdown (ocmsmenu supports nested items via `itm`) + containing two buttons — Benutzerverwaltung (fn=usr) and Systemüberwachung (fn=admin) — both + restricted to users whose fds_sys authorization exceeds 4. One auth check gates the whole + dropdown; it (and therefore both module scripts) is only added after that check resolves, so + lower-privileged users never see it or fetch either module's code. Called from fis_main_go.js once the DOM (and the initial main menu) is ready. */ -$fis.addAdminMenuIfAuthorized = function () { - if ($('#m_admin').length > 0) { return; } // idempotent +$fis.addAdminMenuButtonsIfAuthorized = function () { + if ($('#m_admin_grp').length > 0) { return; } // idempotent $fis.getAuth('fds_sys').then(function (auth) { - if ((auth || -1) > 4 && $('#m_admin').length === 0) { - $ocms.ocmsmenu.push({ lbl: $t.m_admin, id: 'm_admin', fnc: 'init:admin', ico: 'glyphicon glyphicon-cog' }); + if ((auth || -1) > 4 && $('#m_admin_grp').length === 0) { + $ocms.ocmsmenu.push({ + lbl: $t.m_admin, id: 'm_admin_grp', ico: 'glyphicon glyphicon-cog', + itm: [ + { lbl: $t.m_usr, id: 'm_usr', fnc: 'init:usr', ico: 'glyphicon glyphicon-user' }, + { lbl: $t.m_admin_status, id: 'm_admin', fnc: 'init:admin', ico: 'glyphicon glyphicon-dashboard' } + ] + }); /* Re-render the main menu cleanly: drop the previously generated menu list (never the .nav-right settings dropdown) and rebuild it from the array. */ $('#mainmenu').children('ul:not(.nav-right)').remove(); @@ -3243,5 +3253,5 @@ $(document).ready(function () { $fis.notifications.init(); $fis.draft.init(); $fis.ov(); - $fis.addAdminMenuIfAuthorized(); + $fis.addAdminMenuButtonsIfAuthorized(); }); diff --git a/Fuchs/wwwroot/web/fis.min.js b/Fuchs/wwwroot/web/fis.min.js index b7b0edc..3c61008 100644 --- a/Fuchs/wwwroot/web/fis.min.js +++ b/Fuchs/wwwroot/web/fis.min.js @@ -1,4 +1,4 @@ var t,e,$t={lng:"de-DE",dn:["So","Mo","Di","Mi","Do","Fr","Sa"],mn:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],ma:["Jan","Feb","Mrz","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],datepattern:"(0[1-9]|1[0-9]|2[0-9]|3[01]).(0[1-9]|1[012]).[0-9]{4}",datetimepattern:"(0[1-9]|1[0-9]|2[0-9]|3[01]).(0[1-9]|1[012]).[0-9]{4}\\s([0-5][0-9]):([0-5][0-9])",dateplaceholder:"dd.MM.yyyy",datetimeplaceholder:"dd.MM.yyyy HH:mm",dateformat:"dd.MM.yyyy",datetimeformat:"dd.MM.yyyy HH:mm",f1:"Der Server hat einen Fehler gemeldet: \n",f2:"Bitte versuchen Sie es erneut.",m0:"Diese Internet-Seite benötigt einen html5-kompatiblen Browser.",m0b:"Unterstützt werden bspw: Internet Explorer ab Version 10, Firefox ab Version 31, Chrome ab Version 31, Safari ab Version 7, Opera ab Version 27",m1:"Dieser Datensatz ist momentan von jemand anderem zur Bearbeitung gesperrt.",m2:"Diese Funktion ist zur Zeit nicht verfügbar",t1:"Eingabe erforderlich.",t2:"Eingabe ist nicht erforderlich.",true:"Ja",false:"Nein",alert:"Hinweis",confirm:"Bestätigen",open:"Öffnen","not implemented":"Diese Funktion in zur Zeit noch nicht verfügbar.",l0:"Anmeldung",l1:"Email / Anmeldename",l2:"Email-Adresse / Anmeldename",l3:"Passwort",l4:"Benutzer",l5:"Wird vom System ermittelt...",l6:"Anmelden",l7:"Passwort vergessen?",l7a:'Die "Passwort vergessen"-Funktion läuft in zwei Schritten ab:\n \nIm ersten Schritt wird eine SMS mit einem Code an die hinterlegte Mobilfunk-Nummer versandt.\nIm zweiten Schritt geben Sie bitte diesen Code in das Formular ein und übermitteln es erneut.\n \nIn beiden Schritten wird aus Sicherheitsgründen kein Fehler angezeigt und auch dann ein erfolgreicher Versand bestätigt, wenn die Kombination aus Email-Adresse und Nachname nicht gefunden wurde und/oder der code falsch ist.',l8:"Keinen Account?",l9:"Anmeldenamen der Email-Adresse wurde nicht erkannt.",l10:"Nachname",l11:"Email-Adresse",l12:"Passwort zusenden",l13:"Das Passwort wurde erfolgreich verschickt",l14:"Das Passwort konnte nicht verschickt werden",l15:"Sie sind nicht berechtigt, diese Funktion auszuführen.",l16:"Sie müssen zunächst einen Account angeben.",l17:"Die Kombination aus Anmeldenamen und Passwort konnte nicht bestätigt werden.",l18:"Es gibt ein Problem mit dem Formular.\nEs kann momentan nicht verarbeitet und versendet werden.",name:"Name",submit:"Senden",cancel:"Abbrechen",noop:"Diese Funktion is noch nicht verfügar."};t=self,e=()=>(()=>{var t={d:(e,n)=>{for(var i in n)t.o(n,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:n[i]})}};t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),t.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),t.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"t",{value:!0})};var e,n={};t.r(n),t.d(n,{AbortError:()=>r,DefaultHttpClient:()=>D,HttpClient:()=>h,HttpError:()=>i,HttpResponse:()=>d,HttpTransportType:()=>U,HubConnection:()=>O,HubConnectionBuilder:()=>tt,HubConnectionState:()=>j,JsonHubProtocol:()=>K,LogLevel:()=>e,MessageType:()=>N,NullLogger:()=>p,Subject:()=>q,TimeoutError:()=>o,TransferFormat:()=>B,VERSION:()=>f});class i extends Error{constructor(t,e){const n=new.target.prototype;super(`${t}: Status code '${e}'`),this.statusCode=e,this.__proto__=n}}class o extends Error{constructor(t="A timeout occurred."){const e=new.target.prototype;super(t),this.__proto__=e}}class r extends Error{constructor(t="An abort occurred."){const e=new.target.prototype;super(t),this.__proto__=e}}class s extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="UnsupportedTransportError",this.__proto__=n}}class a extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="DisabledTransportError",this.__proto__=n}}class c extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.transport=e,this.errorType="FailedToStartTransportError",this.__proto__=n}}class l extends Error{constructor(t){const e=new.target.prototype;super(t),this.errorType="FailedToNegotiateWithServerError",this.__proto__=e}}class u extends Error{constructor(t,e){const n=new.target.prototype;super(t),this.innerErrors=e,this.__proto__=n}}class d{constructor(t,e,n){this.statusCode=t,this.statusText=e,this.content=n}}class h{get(t,e){return this.send({...e,method:"GET",url:t})}post(t,e){return this.send({...e,method:"POST",url:t})}delete(t,e){return this.send({...e,method:"DELETE",url:t})}getCookieString(t){return""}}!function(t){t[t.Trace=0]="Trace",t[t.Debug=1]="Debug",t[t.Information=2]="Information",t[t.Warning=3]="Warning",t[t.Error=4]="Error",t[t.Critical=5]="Critical",t[t.None=6]="None"}(e||(e={}));class p{constructor(){}log(t,e){}}p.instance=new p;const f="10.0.0";class m{static isRequired(t,e){if(null==t)throw new Error(`The '${e}' argument is required.`)}static isNotEmpty(t,e){if(!t||t.match(/^\s*$/))throw new Error(`The '${e}' argument should not be empty.`)}static isIn(t,e,n){if(!(t in e))throw new Error(`Unknown ${n} value: ${t}.`)}}class g{static get isBrowser(){return!g.isNode&&"object"==typeof window&&"object"==typeof window.document}static get isWebWorker(){return!g.isNode&&"object"==typeof self&&"importScripts"in self}static get isReactNative(){return!g.isNode&&"object"==typeof window&&void 0===window.document}static get isNode(){return"undefined"!=typeof process&&process.release&&"node"===process.release.name}}function y(t,e){let n="";return v(t)?(n=`Binary data of length ${t.byteLength}`,e&&(n+=`. Content: '${function(t){const e=new Uint8Array(t);let n="";return e.forEach((t=>{n+=`0x${t<16?"0":""}${t.toString(16)} `})),n.substring(0,n.length-1)}(t)}'`)):"string"==typeof t&&(n=`String data of length ${t.length}`,e&&(n+=`. Content: '${t}'`)),n}function v(t){return t&&"undefined"!=typeof ArrayBuffer&&(t instanceof ArrayBuffer||t.constructor&&"ArrayBuffer"===t.constructor.name)}async function b(t,n,i,o,r,s){const a={},[c,l]=C();a[c]=l,t.log(e.Trace,`(${n} transport) sending data. ${y(r,s.logMessageContent)}.`);const u=v(r)?"arraybuffer":"text",d=await i.post(o,{content:r,headers:{...a,...s.headers},responseType:u,timeout:s.timeout,withCredentials:s.withCredentials});t.log(e.Trace,`(${n} transport) request complete. Response status: ${d.statusCode}.`)}class ${constructor(t,e){this.i=t,this.h=e}dispose(){const t=this.i.observers.indexOf(this.h);t>-1&&this.i.observers.splice(t,1),0===this.i.observers.length&&this.i.cancelCallback&&this.i.cancelCallback().catch((t=>{}))}}class w{constructor(t){this.l=t,this.out=console}log(t,n){if(t>=this.l){const i=`[${(new Date).toISOString()}] ${e[t]}: ${n}`;switch(t){case e.Critical:case e.Error:this.out.error(i);break;case e.Warning:this.out.warn(i);break;case e.Information:this.out.info(i);break;default:this.out.log(i)}}}}function C(){let t="X-SignalR-User-Agent";return g.isNode&&(t="User-Agent"),[t,S(f,_(),g.isNode?"NodeJS":"Browser",x())]}function S(t,e,n,i){let o="Microsoft SignalR/";const r=t.split(".");return o+=`${r[0]}.${r[1]}`,o+=` (${t}; `,o+=e&&""!==e?`${e}; `:"Unknown OS; ",o+=`${n}`,o+=i?`; ${i}`:"; Unknown Runtime Version",o+=")",o}function _(){if(!g.isNode)return"";switch(process.platform){case"win32":return"Windows NT";case"darwin":return"macOS";case"linux":return"Linux";default:return process.platform}}function x(){if(g.isNode)return process.versions.node}function T(t){return t.stack?t.stack:t.message?t.message:`${t}`}class E extends h{constructor(e){if(super(),this.u=e,"undefined"==typeof fetch||g.isNode){const t=require;this.p=new(t("tough-cookie").CookieJar),"undefined"==typeof fetch?this.m=t("node-fetch"):this.m=fetch,this.m=t("fetch-cookie")(this.m,this.p)}else this.m=fetch.bind(function(){if("undefined"!=typeof globalThis)return globalThis;if("undefined"!=typeof self)return self;if("undefined"!=typeof window)return window;if(void 0!==t.g)return t.g;throw new Error("could not find global")}());if("undefined"==typeof AbortController){const t=require;this.v=t("abort-controller")}else this.v=AbortController}async send(t){if(t.abortSignal&&t.abortSignal.aborted)throw new r;if(!t.method)throw new Error("No method defined.");if(!t.url)throw new Error("No url defined.");const n=new this.v;let s;t.abortSignal&&(t.abortSignal.onabort=()=>{n.abort(),s=new r});let a,c=null;if(t.timeout){const i=t.timeout;c=setTimeout((()=>{n.abort(),this.u.log(e.Warning,"Timeout from HTTP request."),s=new o}),i)}""===t.content&&(t.content=void 0),t.content&&(t.headers=t.headers||{},v(t.content)?t.headers["Content-Type"]="application/octet-stream":t.headers["Content-Type"]="text/plain;charset=UTF-8");try{a=await this.m(t.url,{body:t.content,cache:"no-cache",credentials:!0===t.withCredentials?"include":"same-origin",headers:{"X-Requested-With":"XMLHttpRequest",...t.headers},method:t.method,mode:"cors",redirect:"follow",signal:n.signal})}catch(t){if(s)throw s;throw this.u.log(e.Warning,`Error from HTTP request. ${t}.`),t}finally{c&&clearTimeout(c),t.abortSignal&&(t.abortSignal.onabort=null)}if(!a.ok){const t=await k(a,"text");throw new i(t||a.statusText,a.status)}const l=k(a,t.responseType),u=await l;return new d(a.status,a.statusText,u)}getCookieString(t){let e="";return g.isNode&&this.p&&this.p.getCookies(t,((t,n)=>e=n.join("; "))),e}}function k(t,e){let n;switch(e){case"arraybuffer":n=t.arrayBuffer();break;case"text":default:n=t.text();break;case"blob":case"document":case"json":throw new Error(`${e} is not supported.`)}return n}class I extends h{constructor(t){super(),this.u=t}send(t){return t.abortSignal&&t.abortSignal.aborted?Promise.reject(new r):t.method?t.url?new Promise(((n,s)=>{const a=new XMLHttpRequest;a.open(t.method,t.url,!0),a.withCredentials=void 0===t.withCredentials||t.withCredentials,a.setRequestHeader("X-Requested-With","XMLHttpRequest"),""===t.content&&(t.content=void 0),t.content&&(v(t.content)?a.setRequestHeader("Content-Type","application/octet-stream"):a.setRequestHeader("Content-Type","text/plain;charset=UTF-8"));const c=t.headers;c&&Object.keys(c).forEach((t=>{a.setRequestHeader(t,c[t])})),t.responseType&&(a.responseType=t.responseType),t.abortSignal&&(t.abortSignal.onabort=()=>{a.abort(),s(new r)}),t.timeout&&(a.timeout=t.timeout),a.onload=()=>{t.abortSignal&&(t.abortSignal.onabort=null),a.status>=200&&a.status<300?n(new d(a.status,a.statusText,a.response||a.responseText)):s(new i(a.response||a.responseText||a.statusText,a.status))},a.onerror=()=>{this.u.log(e.Warning,`Error from HTTP request. ${a.status}: ${a.statusText}.`),s(new i(a.statusText,a.status))},a.ontimeout=()=>{this.u.log(e.Warning,"Timeout from HTTP request."),s(new o)},a.send(t.content)})):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}}class D extends h{constructor(t){if(super(),"undefined"!=typeof fetch||g.isNode)this.$=new E(t);else{if("undefined"==typeof XMLHttpRequest)throw new Error("No usable HttpClient found.");this.$=new I(t)}}send(t){return t.abortSignal&&t.abortSignal.aborted?Promise.reject(new r):t.method?t.url?this.$.send(t):Promise.reject(new Error("No url defined.")):Promise.reject(new Error("No method defined."))}getCookieString(t){return this.$.getCookieString(t)}}class A{static write(t){return`${t}${A.RecordSeparator}`}static parse(t){if(t[t.length-1]!==A.RecordSeparator)throw new Error("Message is incomplete.");const e=t.split(A.RecordSeparator);return e.pop(),e}}A.RecordSeparatorCode=30,A.RecordSeparator=String.fromCharCode(A.RecordSeparatorCode);class P{writeHandshakeRequest(t){return A.write(JSON.stringify(t))}parseHandshakeResponse(t){let e,n;if(v(t)){const i=new Uint8Array(t),o=i.indexOf(A.RecordSeparatorCode);if(-1===o)throw new Error("Message is incomplete.");const r=o+1;e=String.fromCharCode.apply(null,Array.prototype.slice.call(i.slice(0,r))),n=i.byteLength>r?i.slice(r).buffer:null}else{const i=t,o=i.indexOf(A.RecordSeparator);if(-1===o)throw new Error("Message is incomplete.");const r=o+1;e=i.substring(0,r),n=i.length>r?i.substring(r):null}const i=A.parse(e),o=JSON.parse(i[0]);if(o.type)throw new Error("Expected a handshake response from the server.");return[n,o]}}var N,j;!function(t){t[t.Invocation=1]="Invocation",t[t.StreamItem=2]="StreamItem",t[t.Completion=3]="Completion",t[t.StreamInvocation=4]="StreamInvocation",t[t.CancelInvocation=5]="CancelInvocation",t[t.Ping=6]="Ping",t[t.Close=7]="Close",t[t.Ack=8]="Ack",t[t.Sequence=9]="Sequence"}(N||(N={}));class q{constructor(){this.observers=[]}next(t){for(const e of this.observers)e.next(t)}error(t){for(const e of this.observers)e.error&&e.error(t)}complete(){for(const t of this.observers)t.complete&&t.complete()}subscribe(t){return this.observers.push(t),new $(this,t)}}class M{constructor(t,e,n){this.C=1e5,this.S=[],this.k=0,this.P=!1,this.T=1,this.I=0,this._=0,this.H=!1,this.D=t,this.R=e,this.C=n}async A(t){const e=this.D.writeMessage(t);let n=Promise.resolve();if(this.U(t)){this.k++;let t=()=>{},i=()=>{};v(e)?this._+=e.byteLength:this._+=e.length,this._>=this.C&&(n=new Promise(((e,n)=>{t=e,i=n}))),this.S.push(new R(e,this.k,t,i))}try{this.H||await this.R.send(e)}catch{this.L()}await n}N(t){let e=-1;for(let n=0;nthis.T?this.R.stop(new Error("Sequence ID greater than amount of messages we've received.")):this.T=t.sequenceId}L(){this.H=!0,this.P=!0}async B(){const t=0!==this.S.length?this.S[0].q:this.k+1;await this.R.send(this.D.writeMessage({type:N.Sequence,sequenceId:t}));const e=this.S;for(const t of e)await this.R.send(t.M);this.H=!1}X(t){null!=t||(t=new Error("Unable to reconnect to server."));for(const e of this.S)e.J(t)}U(t){switch(t.type){case N.Invocation:case N.StreamItem:case N.Completion:case N.StreamInvocation:case N.CancelInvocation:return!0;case N.Close:case N.Sequence:case N.Ping:case N.Ack:return!1}}O(){void 0===this.V&&(this.V=setTimeout((async()=>{try{this.H||await this.R.send(this.D.writeMessage({type:N.Ack,sequenceId:this.I}))}catch{}clearTimeout(this.V),this.V=void 0}),1e3))}}class R{constructor(t,e,n,i){this.M=t,this.q=e,this.j=n,this.J=i}}!function(t){t.Disconnected="Disconnected",t.Connecting="Connecting",t.Connected="Connected",t.Disconnecting="Disconnecting",t.Reconnecting="Reconnecting"}(j||(j={}));class O{static create(t,e,n,i,o,r,s){return new O(t,e,n,i,o,r,s)}constructor(t,n,i,o,r,s,a){this.K=0,this.G=()=>{this.u.log(e.Warning,"The page is being frozen, this will likely lead to the connection being closed and messages being lost. For more information see the docs at https://learn.microsoft.com/aspnet/core/signalr/javascript-client#bsleep")},m.isRequired(t,"connection"),m.isRequired(n,"logger"),m.isRequired(i,"protocol"),this.serverTimeoutInMilliseconds=null!=r?r:3e4,this.keepAliveIntervalInMilliseconds=null!=s?s:15e3,this.Y=null!=a?a:1e5,this.u=n,this.D=i,this.connection=t,this.Z=o,this.tt=new P,this.connection.onreceive=t=>this.et(t),this.connection.onclose=t=>this.st(t),this.it={},this.nt={},this.rt=[],this.ot=[],this.ht=[],this.ct=0,this.lt=!1,this.ut=j.Disconnected,this.dt=!1,this.ft=this.D.writeMessage({type:N.Ping})}get state(){return this.ut}get connectionId(){return this.connection&&this.connection.connectionId||null}get baseUrl(){return this.connection.baseUrl||""}set baseUrl(t){if(this.ut!==j.Disconnected&&this.ut!==j.Reconnecting)throw new Error("The HubConnection must be in the Disconnected or Reconnecting state to change the url.");if(!t)throw new Error("The HubConnection url must be a valid url.");this.connection.baseUrl=t}start(){return this.wt=this.gt(),this.wt}async gt(){if(this.ut!==j.Disconnected)return Promise.reject(new Error("Cannot start a HubConnection that is not in the 'Disconnected' state."));this.ut=j.Connecting,this.u.log(e.Debug,"Starting HubConnection.");try{await this.yt(),g.isBrowser&&window.document.addEventListener("freeze",this.G),this.ut=j.Connected,this.dt=!0,this.u.log(e.Debug,"HubConnection connected successfully.")}catch(t){return this.ut=j.Disconnected,this.u.log(e.Debug,`HubConnection failed to start successfully because of error '${t}'.`),Promise.reject(t)}}async yt(){this.vt=void 0,this.lt=!1;const t=new Promise(((t,e)=>{this.bt=t,this.Et=e}));await this.connection.start(this.D.transferFormat);try{let n=this.D.version;this.connection.features.reconnect||(n=1);const i={protocol:this.D.name,version:n};if(this.u.log(e.Debug,"Sending handshake request."),await this.$t(this.tt.writeHandshakeRequest(i)),this.u.log(e.Information,`Using HubProtocol '${this.D.name}'.`),this.Ct(),this.St(),this.kt(),await t,this.vt)throw this.vt;!!this.connection.features.reconnect&&(this.Pt=new M(this.D,this.connection,this.Y),this.connection.features.disconnected=this.Pt.L.bind(this.Pt),this.connection.features.resend=()=>{if(this.Pt)return this.Pt.B()}),this.connection.features.inherentKeepAlive||await this.$t(this.ft)}catch(t){throw this.u.log(e.Debug,`Hub handshake failed with error '${t}' during start(). Stopping HubConnection.`),this.Ct(),this.Tt(),await this.connection.stop(t),t}}async stop(){const t=this.wt;this.connection.features.reconnect=!1,this.It=this._t(),await this.It;try{await t}catch(t){}}_t(t){if(this.ut===j.Disconnected)return this.u.log(e.Debug,`Call to HubConnection.stop(${t}) ignored because it is already in the disconnected state.`),Promise.resolve();if(this.ut===j.Disconnecting)return this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnecting state.`),this.It;const n=this.ut;return this.ut=j.Disconnecting,this.u.log(e.Debug,"Stopping HubConnection."),this.Ht?(this.u.log(e.Debug,"Connection stopped during reconnect delay. Done reconnecting."),clearTimeout(this.Ht),this.Ht=void 0,this.Dt(),Promise.resolve()):(n===j.Connected&&this.Rt(),this.Ct(),this.Tt(),this.vt=t||new r("The connection was stopped before the hub handshake could complete."),this.connection.stop(t))}async Rt(){try{await this.xt(this.At())}catch{}}stream(t,...e){const[n,i]=this.Ut(e),o=this.Lt(t,e,i);let r;const s=new q;return s.cancelCallback=()=>{const t=this.Nt(o.invocationId);return delete this.it[o.invocationId],r.then((()=>this.xt(t)))},this.it[o.invocationId]=(t,e)=>{e?s.error(e):t&&(t.type===N.Completion?t.error?s.error(new Error(t.error)):s.complete():s.next(t.item))},r=this.xt(o).catch((t=>{s.error(t),delete this.it[o.invocationId]})),this.qt(n,r),s}$t(t){return this.kt(),this.connection.send(t)}xt(t){return this.Pt?this.Pt.A(t):this.$t(this.D.writeMessage(t))}send(t,...e){const[n,i]=this.Ut(e),o=this.xt(this.Mt(t,e,!0,i));return this.qt(n,o),o}invoke(t,...e){const[n,i]=this.Ut(e),o=this.Mt(t,e,!1,i);return new Promise(((t,e)=>{this.it[o.invocationId]=(n,i)=>{i?e(i):n&&(n.type===N.Completion?n.error?e(new Error(n.error)):t(n.result):e(new Error(`Unexpected message type: ${n.type}`)))};const i=this.xt(o).catch((t=>{e(t),delete this.it[o.invocationId]}));this.qt(n,i)}))}on(t,e){t&&e&&(t=t.toLowerCase(),this.nt[t]||(this.nt[t]=[]),-1===this.nt[t].indexOf(e)&&this.nt[t].push(e))}off(t,e){if(!t)return;t=t.toLowerCase();const n=this.nt[t];if(n)if(e){const i=n.indexOf(e);-1!==i&&(n.splice(i,1),0===n.length&&delete this.nt[t])}else delete this.nt[t]}onclose(t){t&&this.rt.push(t)}onreconnecting(t){t&&this.ot.push(t)}onreconnected(t){t&&this.ht.push(t)}et(t){if(this.Ct(),this.lt||(t=this.jt(t),this.lt=!0),t){const n=this.D.parseMessages(t,this.u);for(const i of n)if(!this.Pt||this.Pt.W(i))switch(i.type){case N.Invocation:this.Wt(i).catch((t=>{this.u.log(e.Error,`Invoke client method threw error: ${T(t)}`)}));break;case N.StreamItem:case N.Completion:{const n=this.it[i.invocationId];if(n){i.type===N.Completion&&delete this.it[i.invocationId];try{n(i)}catch(t){this.u.log(e.Error,`Stream callback threw error: ${T(t)}`)}}break}case N.Ping:break;case N.Close:{this.u.log(e.Information,"Close message received from server.");const t=i.error?new Error("Server returned an error on close: "+i.error):void 0;!0===i.allowReconnect?this.connection.stop(t):this.It=this._t(t);break}case N.Ack:this.Pt&&this.Pt.N(i);break;case N.Sequence:this.Pt&&this.Pt.F(i);break;default:this.u.log(e.Warning,`Invalid message type: ${i.type}.`)}}this.St()}jt(t){let n,i;try{[i,n]=this.tt.parseHandshakeResponse(t)}catch(t){const n="Error parsing handshake response: "+t;this.u.log(e.Error,n);const i=new Error(n);throw this.Et(i),i}if(n.error){const t="Server returned handshake error: "+n.error;this.u.log(e.Error,t);const i=new Error(t);throw this.Et(i),i}return this.u.log(e.Debug,"Server handshake complete."),this.bt(),i}kt(){this.connection.features.inherentKeepAlive||(this.K=(new Date).getTime()+this.keepAliveIntervalInMilliseconds,this.Tt())}St(){if(!this.connection.features||!this.connection.features.inherentKeepAlive){this.Ot=setTimeout((()=>this.serverTimeout()),this.serverTimeoutInMilliseconds);let t=this.K-(new Date).getTime();if(t<0)return void(this.ut===j.Connected&&this.Ft());void 0===this.Bt&&(t<0&&(t=0),this.Bt=setTimeout((async()=>{this.ut===j.Connected&&await this.Ft()}),t))}}serverTimeout(){this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."))}async Wt(t){const n=t.target.toLowerCase(),i=this.nt[n];if(!i)return this.u.log(e.Warning,`No client method with the name '${n}' found.`),void(t.invocationId&&(this.u.log(e.Warning,`No result given for '${n}' method and invocation ID '${t.invocationId}'.`),await this.xt(this.Xt(t.invocationId,"Client didn't provide a result.",null))));const o=i.slice(),r=!!t.invocationId;let s,a,c;for(const i of o)try{const o=s;s=await i.apply(this,t.arguments),r&&s&&o&&(this.u.log(e.Error,`Multiple results provided for '${n}'. Sending error to server.`),c=this.Xt(t.invocationId,"Client provided multiple results.",null)),a=void 0}catch(t){a=t,this.u.log(e.Error,`A callback for the method '${n}' threw error '${t}'.`)}c?await this.xt(c):r?(a?c=this.Xt(t.invocationId,`${a}`,null):void 0!==s?c=this.Xt(t.invocationId,null,s):(this.u.log(e.Warning,`No result given for '${n}' method and invocation ID '${t.invocationId}'.`),c=this.Xt(t.invocationId,"Client didn't provide a result.",null)),await this.xt(c)):s&&this.u.log(e.Error,`Result given for '${n}' method but server is not expecting a result.`)}st(t){this.u.log(e.Debug,`HubConnection.connectionClosed(${t}) called while in state ${this.ut}.`),this.vt=this.vt||t||new r("The underlying connection was closed before the hub handshake could complete."),this.bt&&this.bt(),this.Jt(t||new Error("Invocation canceled due to the underlying connection being closed.")),this.Ct(),this.Tt(),this.ut===j.Disconnecting?this.Dt(t):this.ut===j.Connected&&this.Z?this.zt(t):this.ut===j.Connected&&this.Dt(t)}Dt(t){if(this.dt){this.ut=j.Disconnected,this.dt=!1,this.Pt&&(this.Pt.X(null!=t?t:new Error("Connection closed.")),this.Pt=void 0),g.isBrowser&&window.document.removeEventListener("freeze",this.G);try{this.rt.forEach((e=>e.apply(this,[t])))}catch(n){this.u.log(e.Error,`An onclose callback called with error '${t}' threw error '${n}'.`)}}}async zt(t){const n=Date.now();let i=0,o=void 0!==t?t:new Error("Attempting to reconnect due to a unknown error."),r=this.Vt(i,0,o);if(null===r)return this.u.log(e.Debug,"Connection not reconnecting because the IRetryPolicy returned null on the first reconnect attempt."),void this.Dt(t);if(this.ut=j.Reconnecting,t?this.u.log(e.Information,`Connection reconnecting because of error '${t}'.`):this.u.log(e.Information,"Connection reconnecting."),0!==this.ot.length){try{this.ot.forEach((e=>e.apply(this,[t])))}catch(n){this.u.log(e.Error,`An onreconnecting callback called with error '${t}' threw error '${n}'.`)}if(this.ut!==j.Reconnecting)return void this.u.log(e.Debug,"Connection left the reconnecting state in onreconnecting callback. Done reconnecting.")}for(;null!==r;){if(this.u.log(e.Information,`Reconnect attempt number ${i+1} will start in ${r} ms.`),await new Promise((t=>{this.Ht=setTimeout(t,r)})),this.Ht=void 0,this.ut!==j.Reconnecting)return void this.u.log(e.Debug,"Connection left the reconnecting state during reconnect delay. Done reconnecting.");try{if(await this.yt(),this.ut=j.Connected,this.u.log(e.Information,"HubConnection reconnected successfully."),0!==this.ht.length)try{this.ht.forEach((t=>t.apply(this,[this.connection.connectionId])))}catch(t){this.u.log(e.Error,`An onreconnected callback called with connectionId '${this.connection.connectionId}; threw error '${t}'.`)}return}catch(t){if(this.u.log(e.Information,`Reconnect attempt failed because of error '${t}'.`),this.ut!==j.Reconnecting)return this.u.log(e.Debug,`Connection moved to the '${this.ut}' from the reconnecting state during reconnect attempt. Done reconnecting.`),void(this.ut===j.Disconnecting&&this.Dt());i++,o=t instanceof Error?t:new Error(t.toString()),r=this.Vt(i,Date.now()-n,o)}}this.u.log(e.Information,`Reconnect retries have been exhausted after ${Date.now()-n} ms and ${i} failed attempts. Connection disconnecting.`),this.Dt()}Vt(t,n,i){try{return this.Z.nextRetryDelayInMilliseconds({elapsedMilliseconds:n,previousRetryCount:t,retryReason:i})}catch(i){return this.u.log(e.Error,`IRetryPolicy.nextRetryDelayInMilliseconds(${t}, ${n}) threw error '${i}'.`),null}}Jt(t){const n=this.it;this.it={},Object.keys(n).forEach((i=>{const o=n[i];try{o(null,t)}catch(n){this.u.log(e.Error,`Stream 'error' callback called with '${t}' threw error: ${T(n)}`)}}))}Tt(){this.Bt&&(clearTimeout(this.Bt),this.Bt=void 0)}Ct(){this.Ot&&clearTimeout(this.Ot)}Mt(t,e,n,i){if(n)return 0!==i.length?{target:t,arguments:e,streamIds:i,type:N.Invocation}:{target:t,arguments:e,type:N.Invocation};{const n=this.ct;return this.ct++,0!==i.length?{target:t,arguments:e,invocationId:n.toString(),streamIds:i,type:N.Invocation}:{target:t,arguments:e,invocationId:n.toString(),type:N.Invocation}}}qt(t,e){if(0!==t.length){e||(e=Promise.resolve());for(const n in t)t[n].subscribe({complete:()=>{e=e.then((()=>this.xt(this.Xt(n))))},error:t=>{let i;i=t instanceof Error?t.message:t&&t.toString?t.toString():"Unknown error",e=e.then((()=>this.xt(this.Xt(n,i))))},next:t=>{e=e.then((()=>this.xt(this.Kt(n,t))))}})}}Ut(t){const e=[],n=[];for(let i=0;i0)&&(e=!1,this.te=await this.Zt()),this.ee(t);const n=await this.Yt.send(t);return e&&401===n.statusCode&&this.Zt?(this.te=await this.Zt(),this.ee(t),await this.Yt.send(t)):n}ee(t){t.headers||(t.headers={}),this.te?t.headers[L.Authorization]=`Bearer ${this.te}`:this.Zt&&t.headers[L.Authorization]&&delete t.headers[L.Authorization]}getCookieString(t){return this.Yt.getCookieString(t)}}var U,B;!function(t){t[t.None=0]="None",t[t.WebSockets=1]="WebSockets",t[t.ServerSentEvents=2]="ServerSentEvents",t[t.LongPolling=4]="LongPolling"}(U||(U={})),function(t){t[t.Text=1]="Text",t[t.Binary=2]="Binary"}(B||(B={}));class W{constructor(){this.se=!1,this.onabort=null}abort(){this.se||(this.se=!0,this.onabort&&this.onabort())}get signal(){return this}get aborted(){return this.se}}class X{get pollAborted(){return this.ie.aborted}constructor(t,e,n){this.$=t,this.u=e,this.ie=new W,this.ne=n,this.re=!1,this.onreceive=null,this.onclose=null}async connect(t,n){if(m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.oe=t,this.u.log(e.Trace,"(LongPolling transport) Connecting."),n===B.Binary&&"undefined"!=typeof XMLHttpRequest&&"string"!=typeof(new XMLHttpRequest).responseType)throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");const[o,r]=C(),s={[o]:r,...this.ne.headers},a={abortSignal:this.ie.signal,headers:s,timeout:1e5,withCredentials:this.ne.withCredentials};n===B.Binary&&(a.responseType="arraybuffer");const c=`${t}&_=${Date.now()}`;this.u.log(e.Trace,`(LongPolling transport) polling: ${c}.`);const l=await this.$.get(c,a);200!==l.statusCode?(this.u.log(e.Error,`(LongPolling transport) Unexpected response code: ${l.statusCode}.`),this.he=new i(l.statusText||"",l.statusCode),this.re=!1):this.re=!0,this.ce=this.ae(this.oe,a)}async ae(t,n){try{for(;this.re;)try{const o=`${t}&_=${Date.now()}`;this.u.log(e.Trace,`(LongPolling transport) polling: ${o}.`);const r=await this.$.get(o,n);204===r.statusCode?(this.u.log(e.Information,"(LongPolling transport) Poll terminated by server."),this.re=!1):200!==r.statusCode?(this.u.log(e.Error,`(LongPolling transport) Unexpected response code: ${r.statusCode}.`),this.he=new i(r.statusText||"",r.statusCode),this.re=!1):r.content?(this.u.log(e.Trace,`(LongPolling transport) data received. ${y(r.content,this.ne.logMessageContent)}.`),this.onreceive&&this.onreceive(r.content)):this.u.log(e.Trace,"(LongPolling transport) Poll timed out, reissuing.")}catch(t){this.re?t instanceof o?this.u.log(e.Trace,"(LongPolling transport) Poll timed out, reissuing."):(this.he=t,this.re=!1):this.u.log(e.Trace,`(LongPolling transport) Poll errored after shutdown: ${t.message}`)}}finally{this.u.log(e.Trace,"(LongPolling transport) Polling complete."),this.pollAborted||this.le()}}async send(t){return this.re?b(this.u,"LongPolling",this.$,this.oe,t,this.ne):Promise.reject(new Error("Cannot send until the transport is connected"))}async stop(){this.u.log(e.Trace,"(LongPolling transport) Stopping polling."),this.re=!1,this.ie.abort();try{await this.ce,this.u.log(e.Trace,`(LongPolling transport) sending DELETE request to ${this.oe}.`);const t={},[n,o]=C();t[n]=o;const r={headers:{...t,...this.ne.headers},timeout:this.ne.timeout,withCredentials:this.ne.withCredentials};let s;try{await this.$.delete(this.oe,r)}catch(t){s=t}s?s instanceof i&&(404===s.statusCode?this.u.log(e.Trace,"(LongPolling transport) A 404 response was returned from sending a DELETE request."):this.u.log(e.Trace,`(LongPolling transport) Error sending a DELETE request: ${s}`)):this.u.log(e.Trace,"(LongPolling transport) DELETE request accepted.")}finally{this.u.log(e.Trace,"(LongPolling transport) Stop finished."),this.le()}}le(){if(this.onclose){let t="(LongPolling transport) Firing onclose event.";this.he&&(t+=" Error: "+this.he),this.u.log(e.Trace,t),this.onclose(this.he)}}}class V{constructor(t,e,n,i){this.$=t,this.te=e,this.u=n,this.ne=i,this.onreceive=null,this.onclose=null}async connect(t,n){return m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.u.log(e.Trace,"(SSE transport) Connecting."),this.oe=t,this.te&&(t+=(t.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(this.te)}`),new Promise(((i,o)=>{let r,s=!1;if(n===B.Text){if(g.isBrowser||g.isWebWorker)r=new this.ne.EventSource(t,{withCredentials:this.ne.withCredentials});else{const e=this.$.getCookieString(t),n={};n.Cookie=e;const[i,o]=C();n[i]=o,r=new this.ne.EventSource(t,{withCredentials:this.ne.withCredentials,headers:{...n,...this.ne.headers}})}try{r.onmessage=t=>{if(this.onreceive)try{this.u.log(e.Trace,`(SSE transport) data received. ${y(t.data,this.ne.logMessageContent)}.`),this.onreceive(t.data)}catch(t){return void this.ue(t)}},r.onerror=t=>{s?this.ue():o(new Error("EventSource failed to connect. The connection could not be found on the server, either the connection ID is not present on the server, or a proxy is refusing/buffering the connection. If you have multiple servers check that sticky sessions are enabled."))},r.onopen=()=>{this.u.log(e.Information,`SSE connected to ${this.oe}`),this.de=r,s=!0,i()}}catch(t){return void o(t)}}else o(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"))}))}async send(t){return this.de?b(this.u,"SSE",this.$,this.oe,t,this.ne):Promise.reject(new Error("Cannot send until the transport is connected"))}stop(){return this.ue(),Promise.resolve()}ue(t){this.de&&(this.de.close(),this.de=void 0,this.onclose&&this.onclose(t))}}class J{constructor(t,e,n,i,o,r){this.u=n,this.Zt=e,this.fe=i,this.pe=o,this.$=t,this.onreceive=null,this.onclose=null,this.we=r}async connect(t,n){let i;return m.isRequired(t,"url"),m.isRequired(n,"transferFormat"),m.isIn(n,B,"transferFormat"),this.u.log(e.Trace,"(WebSockets transport) Connecting."),this.Zt&&(i=await this.Zt()),new Promise(((o,r)=>{let s;t=t.replace(/^http/,"ws");const a=this.$.getCookieString(t);let c=!1;if(g.isNode||g.isReactNative){const e={},[n,o]=C();e[n]=o,i&&(e[L.Authorization]=`Bearer ${i}`),a&&(e[L.Cookie]=a),s=new this.pe(t,void 0,{headers:{...e,...this.we}})}else i&&(t+=(t.indexOf("?")<0?"?":"&")+`access_token=${encodeURIComponent(i)}`);s||(s=new this.pe(t)),n===B.Binary&&(s.binaryType="arraybuffer"),s.onopen=n=>{this.u.log(e.Information,`WebSocket connected to ${t}.`),this.ge=s,c=!0,o()},s.onerror=t=>{let n=null;n="undefined"!=typeof ErrorEvent&&t instanceof ErrorEvent?t.error:"There was an error with the transport",this.u.log(e.Information,`(WebSockets transport) ${n}.`)},s.onmessage=t=>{if(this.u.log(e.Trace,`(WebSockets transport) data received. ${y(t.data,this.fe)}.`),this.onreceive)try{this.onreceive(t.data)}catch(t){return void this.ue(t)}},s.onclose=t=>{if(c)this.ue(t);else{let e=null;e="undefined"!=typeof ErrorEvent&&t instanceof ErrorEvent?t.error:"WebSocket failed to connect. The connection could not be found on the server, either the endpoint may not be a SignalR endpoint, the connection ID is not present on the server, or there is a proxy blocking WebSockets. If you have multiple servers check that sticky sessions are enabled.",r(new Error(e))}}}))}send(t){return this.ge&&this.ge.readyState===this.pe.OPEN?(this.u.log(e.Trace,`(WebSockets transport) sending data. ${y(t,this.fe)}.`),this.ge.send(t),Promise.resolve()):Promise.reject("WebSocket is not in the OPEN state")}stop(){return this.ge&&this.ue(void 0),Promise.resolve()}ue(t){this.ge&&(this.ge.onclose=()=>{},this.ge.onmessage=()=>{},this.ge.onerror=()=>{},this.ge.close(),this.ge=void 0),this.u.log(e.Trace,"(WebSockets transport) socket closed."),this.onclose&&(!this.me(t)||!1!==t.wasClean&&1e3===t.code?t instanceof Error?this.onclose(t):this.onclose():this.onclose(new Error(`WebSocket closed with status code: ${t.code} (${t.reason||"no reason given"}).`)))}me(t){return t&&"boolean"==typeof t.wasClean&&"number"==typeof t.code}}class Z{constructor(t,n={}){var i;if(this.ye=()=>{},this.features={},this.ve=1,m.isRequired(t,"url"),this.u=void 0===(i=n.logger)?new w(e.Information):null===i?p.instance:void 0!==i.log?i:new w(i),this.baseUrl=this.be(t),(n=n||{}).logMessageContent=void 0!==n.logMessageContent&&n.logMessageContent,"boolean"!=typeof n.withCredentials&&void 0!==n.withCredentials)throw new Error("withCredentials option was not a 'boolean' or 'undefined' value");n.withCredentials=void 0===n.withCredentials||n.withCredentials,n.timeout=void 0===n.timeout?1e5:n.timeout;let o=null,r=null;if(g.isNode){const t=require;o=t("ws"),r=t("eventsource")}g.isNode||"undefined"==typeof WebSocket||n.WebSocket?g.isNode&&!n.WebSocket&&o&&(n.WebSocket=o):n.WebSocket=WebSocket,g.isNode||"undefined"==typeof EventSource||n.EventSource?g.isNode&&!n.EventSource&&void 0!==r&&(n.EventSource=r):n.EventSource=EventSource,this.$=new F(n.httpClient||new D(this.u),n.accessTokenFactory),this.ut="Disconnected",this.dt=!1,this.ne=n,this.onreceive=null,this.onclose=null}async start(t){if(t=t||B.Binary,m.isIn(t,B,"transferFormat"),this.u.log(e.Debug,`Starting connection with transfer format '${B[t]}'.`),"Disconnected"!==this.ut)return Promise.reject(new Error("Cannot start an HttpConnection that is not in the 'Disconnected' state."));if(this.ut="Connecting",this.Ee=this.yt(t),await this.Ee,"Disconnecting"===this.ut){const t="Failed to start the HttpConnection before stop() was called.";return this.u.log(e.Error,t),await this.It,Promise.reject(new r(t))}if("Connected"!==this.ut){const t="HttpConnection.startInternal completed gracefully but didn't enter the connection into the connected state!";return this.u.log(e.Error,t),Promise.reject(new r(t))}this.dt=!0}send(t){return"Connected"!==this.ut?Promise.reject(new Error("Cannot send data if the connection is not in the 'Connected' State.")):(this.$e||(this.$e=new Y(this.transport)),this.$e.send(t))}async stop(t){return"Disconnected"===this.ut?(this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnected state.`),Promise.resolve()):"Disconnecting"===this.ut?(this.u.log(e.Debug,`Call to HttpConnection.stop(${t}) ignored because the connection is already in the disconnecting state.`),this.It):(this.ut="Disconnecting",this.It=new Promise((t=>{this.ye=t})),await this._t(t),void await this.It)}async _t(t){this.Ce=t;try{await this.Ee}catch(t){}if(this.transport){try{await this.transport.stop()}catch(t){this.u.log(e.Error,`HttpConnection.transport.stop() threw error '${t}'.`),this.Se()}this.transport=void 0}else this.u.log(e.Debug,"HttpConnection.transport is undefined in HttpConnection.stop() because start() failed.")}async yt(t){let n=this.baseUrl;this.Zt=this.ne.accessTokenFactory,this.$.Zt=this.Zt;try{if(this.ne.skipNegotiation){if(this.ne.transport!==U.WebSockets)throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");this.transport=this.ke(U.WebSockets),await this.Pe(n,t)}else{let e=null,i=0;do{if(e=await this.Te(n),"Disconnecting"===this.ut||"Disconnected"===this.ut)throw new r("The connection was stopped during negotiation.");if(e.error)throw new Error(e.error);if(e.ProtocolVersion)throw new Error("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.");if(e.url&&(n=e.url),e.accessToken){const t=e.accessToken;this.Zt=()=>t,this.$.te=t,this.$.Zt=void 0}i++}while(e.url&&i<100);if(100===i&&e.url)throw new Error("Negotiate redirection limit exceeded.");await this.Ie(n,this.ne.transport,e,t)}this.transport instanceof X&&(this.features.inherentKeepAlive=!0),"Connecting"===this.ut&&(this.u.log(e.Debug,"The HttpConnection connected successfully."),this.ut="Connected")}catch(t){return this.u.log(e.Error,"Failed to start the connection: "+t),this.ut="Disconnected",this.transport=void 0,this.ye(),Promise.reject(t)}}async Te(t){const n={},[o,r]=C();n[o]=r;const s=this._e(t);this.u.log(e.Debug,`Sending negotiation request: ${s}.`);try{const t=await this.$.post(s,{content:"",headers:{...n,...this.ne.headers},timeout:this.ne.timeout,withCredentials:this.ne.withCredentials});if(200!==t.statusCode)return Promise.reject(new Error(`Unexpected status code returned from negotiate '${t.statusCode}'`));const e=JSON.parse(t.content);return(!e.negotiateVersion||e.negotiateVersion<1)&&(e.connectionToken=e.connectionId),e.useStatefulReconnect&&!0!==this.ne.He?Promise.reject(new l("Client didn't negotiate Stateful Reconnect but the server did.")):e}catch(t){let n="Failed to complete negotiation with the server: "+t;return t instanceof i&&404===t.statusCode&&(n+=" Either this is not a SignalR endpoint or there is a proxy blocking the connection."),this.u.log(e.Error,n),Promise.reject(new l(n))}}De(t,e){return e?t+(-1===t.indexOf("?")?"?":"&")+`id=${e}`:t}async Ie(t,n,i,o){let s=this.De(t,i.connectionToken);if(this.Re(n))return this.u.log(e.Debug,"Connection was provided an instance of ITransport, using that directly."),this.transport=n,await this.Pe(s,o),void(this.connectionId=i.connectionId);const a=[],l=i.availableTransports||[];let d=i;for(const i of l){const l=this.xe(i,n,o,!0===(null==d?void 0:d.useStatefulReconnect));if(l instanceof Error)a.push(`${i.transport} failed:`),a.push(l);else if(this.Re(l)){if(this.transport=l,!d){try{d=await this.Te(t)}catch(t){return Promise.reject(t)}s=this.De(t,d.connectionToken)}try{return await this.Pe(s,o),void(this.connectionId=d.connectionId)}catch(t){if(this.u.log(e.Error,`Failed to start the transport '${i.transport}': ${t}`),d=void 0,a.push(new c(`${i.transport} failed: ${t}`,U[i.transport])),"Connecting"!==this.ut){const t="Failed to select transport before stop() was called.";return this.u.log(e.Debug,t),Promise.reject(new r(t))}}}}return a.length>0?Promise.reject(new u(`Unable to connect to the server with any of the available transports. ${a.join(" ")}`,a)):Promise.reject(new Error("None of the transports supported by the client are supported by the server."))}ke(t){switch(t){case U.WebSockets:if(!this.ne.WebSocket)throw new Error("'WebSocket' is not supported in your environment.");return new J(this.$,this.Zt,this.u,this.ne.logMessageContent,this.ne.WebSocket,this.ne.headers||{});case U.ServerSentEvents:if(!this.ne.EventSource)throw new Error("'EventSource' is not supported in your environment.");return new V(this.$,this.$.te,this.u,this.ne);case U.LongPolling:return new X(this.$,this.u,this.ne);default:throw new Error(`Unknown transport: ${t}.`)}}Pe(t,e){return this.transport.onreceive=this.onreceive,this.features.reconnect?this.transport.onclose=async n=>{let i=!1;if(this.features.reconnect){try{this.features.disconnected(),await this.transport.connect(t,e),await this.features.resend()}catch{i=!0}i&&this.Se(n)}else this.Se(n)}:this.transport.onclose=t=>this.Se(t),this.transport.connect(t,e)}xe(t,n,i,o){const r=U[t.transport];if(null==r)return this.u.log(e.Debug,`Skipping transport '${t.transport}' because it is not supported by this client.`),new Error(`Skipping transport '${t.transport}' because it is not supported by this client.`);if(!function(t,e){return!t||!!(e&t)}(n,r))return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it was disabled by the client.`),new a(`'${U[r]}' is disabled by the client.`,r);if(!(t.transferFormats.map((t=>B[t])).indexOf(i)>=0))return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it does not support the requested transfer format '${B[i]}'.`),new Error(`'${U[r]}' does not support ${B[i]}.`);if(r===U.WebSockets&&!this.ne.WebSocket||r===U.ServerSentEvents&&!this.ne.EventSource)return this.u.log(e.Debug,`Skipping transport '${U[r]}' because it is not supported in your environment.'`),new s(`'${U[r]}' is not supported in your environment.`,r);this.u.log(e.Debug,`Selecting transport '${U[r]}'.`);try{return this.features.reconnect=r===U.WebSockets?o:void 0,this.ke(r)}catch(t){return t}}Re(t){return t&&"object"==typeof t&&"connect"in t}Se(t){if(this.u.log(e.Debug,`HttpConnection.stopConnection(${t}) called while in state ${this.ut}.`),this.transport=void 0,t=this.Ce||t,this.Ce=void 0,"Disconnected"!==this.ut){if("Connecting"===this.ut)throw this.u.log(e.Warning,`Call to HttpConnection.stopConnection(${t}) was ignored because the connection is still in the connecting state.`),new Error(`HttpConnection.stopConnection(${t}) was called while the connection is still in the connecting state.`);if("Disconnecting"===this.ut&&this.ye(),t?this.u.log(e.Error,`Connection disconnected with error '${t}'.`):this.u.log(e.Information,"Connection disconnected."),this.$e&&(this.$e.stop().catch((t=>{this.u.log(e.Error,`TransportSendQueue.stop() threw error '${t}'.`)})),this.$e=void 0),this.connectionId=void 0,this.ut="Disconnected",this.dt){this.dt=!1;try{this.onclose&&this.onclose(t)}catch(n){this.u.log(e.Error,`HttpConnection.onclose(${t}) threw error '${n}'.`)}}}else this.u.log(e.Debug,`Call to HttpConnection.stopConnection(${t}) was ignored because the connection is already in the disconnected state.`)}be(t){if(0===t.lastIndexOf("https://",0)||0===t.lastIndexOf("http://",0))return t;if(!g.isBrowser)throw new Error(`Cannot resolve '${t}'.`);const n=window.document.createElement("a");return n.href=t,this.u.log(e.Information,`Normalizing '${t}' to '${n.href}'.`),n.href}_e(t){const e=new URL(t);e.pathname.endsWith("/")?e.pathname+="negotiate":e.pathname+="/negotiate";const n=new URLSearchParams(e.searchParams);return n.has("negotiateVersion")||n.append("negotiateVersion",this.ve.toString()),n.has("useStatefulReconnect")?"true"===n.get("useStatefulReconnect")&&(this.ne.He=!0):!0===this.ne.He&&n.append("useStatefulReconnect","true"),e.search=n.toString(),e.toString()}}class Y{constructor(t){this.Ae=t,this.Ue=[],this.Le=!0,this.Ne=new Q,this.qe=new Q,this.Me=this.je()}send(t){return this.We(t),this.qe||(this.qe=new Q),this.qe.promise}stop(){return this.Le=!1,this.Ne.resolve(),this.Me}We(t){if(this.Ue.length&&typeof this.Ue[0]!=typeof t)throw new Error(`Expected data to be of type ${typeof this.Ue} but was of type ${typeof t}`);this.Ue.push(t),this.Ne.resolve()}async je(){for(;;){if(await this.Ne.promise,!this.Le){this.qe&&this.qe.reject("Connection stopped.");break}this.Ne=new Q;const t=this.qe;this.qe=void 0;const e="string"==typeof this.Ue[0]?this.Ue.join(""):Y.Oe(this.Ue);this.Ue.length=0;try{await this.Ae.send(e),t.resolve()}catch(e){t.reject(e)}}}static Oe(t){const e=t.map((t=>t.byteLength)).reduce(((t,e)=>t+e)),n=new Uint8Array(e);let i=0;for(const e of t)n.set(new Uint8Array(e),i),i+=e.byteLength;return n.buffer}}class Q{constructor(){this.promise=new Promise(((t,e)=>[this.j,this.Fe]=[t,e]))}resolve(){this.j()}reject(t){this.Fe(t)}}class K{constructor(){this.name="json",this.version=2,this.transferFormat=B.Text}parseMessages(t,n){if("string"!=typeof t)throw new Error("Invalid input for JSON hub protocol. Expected a string.");if(!t)return[];null===n&&(n=p.instance);const i=A.parse(t),o=[];for(const t of i){const i=JSON.parse(t);if("number"!=typeof i.type)throw new Error("Invalid payload.");switch(i.type){case N.Invocation:this.U(i);break;case N.StreamItem:this.Be(i);break;case N.Completion:this.Xe(i);break;case N.Ping:case N.Close:break;case N.Ack:this.Je(i);break;case N.Sequence:this.ze(i);break;default:n.log(e.Information,"Unknown message type '"+i.type+"' ignored.");continue}o.push(i)}return o}writeMessage(t){return A.write(JSON.stringify(t))}U(t){this.Ve(t.target,"Invalid payload for Invocation message."),void 0!==t.invocationId&&this.Ve(t.invocationId,"Invalid payload for Invocation message.")}Be(t){if(this.Ve(t.invocationId,"Invalid payload for StreamItem message."),void 0===t.item)throw new Error("Invalid payload for StreamItem message.")}Xe(t){if(t.result&&t.error)throw new Error("Invalid payload for Completion message.");!t.result&&t.error&&this.Ve(t.error,"Invalid payload for Completion message."),this.Ve(t.invocationId,"Invalid payload for Completion message.")}Je(t){if("number"!=typeof t.sequenceId)throw new Error("Invalid SequenceId for Ack message.")}ze(t){if("number"!=typeof t.sequenceId)throw new Error("Invalid SequenceId for Sequence message.")}Ve(t,e){if("string"!=typeof t||""===t)throw new Error(e)}}const G={trace:e.Trace,debug:e.Debug,info:e.Information,information:e.Information,warn:e.Warning,warning:e.Warning,error:e.Error,critical:e.Critical,none:e.None};class tt{configureLogging(t){if(m.isRequired(t,"logging"),void 0!==t.log)this.logger=t;else if("string"==typeof t){const e=function(t){const e=G[t.toLowerCase()];if(void 0!==e)return e;throw new Error(`Unknown log level: ${t}`)}(t);this.logger=new w(e)}else this.logger=new w(t);return this}withUrl(t,e){return m.isRequired(t,"url"),m.isNotEmpty(t,"url"),this.url=t,this.httpConnectionOptions="object"==typeof e?{...this.httpConnectionOptions,...e}:{...this.httpConnectionOptions,transport:e},this}withHubProtocol(t){return m.isRequired(t,"protocol"),this.protocol=t,this}withAutomaticReconnect(t){if(this.reconnectPolicy)throw new Error("A reconnectPolicy has already been set.");return t?Array.isArray(t)?this.reconnectPolicy=new z(t):this.reconnectPolicy=t:this.reconnectPolicy=new z,this}withServerTimeout(t){return m.isRequired(t,"milliseconds"),this.Ke=t,this}withKeepAliveInterval(t){return m.isRequired(t,"milliseconds"),this.Ge=t,this}withStatefulReconnect(t){return void 0===this.httpConnectionOptions&&(this.httpConnectionOptions={}),this.httpConnectionOptions.He=!0,this.Y=null==t?void 0:t.bufferSize,this}build(){const t=this.httpConnectionOptions||{};if(void 0===t.logger&&(t.logger=this.logger),!this.url)throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.");const e=new Z(this.url,t);return O.create(e,this.logger||p.instance,this.protocol||new K,this.reconnectPolicy,this.Ke,this.Ge,this.Y)}}return Uint8Array.prototype.indexOf||Object.defineProperty(Uint8Array.prototype,"indexOf",{value:Array.prototype.indexOf,writable:!0}),Uint8Array.prototype.slice||Object.defineProperty(Uint8Array.prototype,"slice",{value:function(t,e){return new Uint8Array(Array.prototype.slice.call(this,t,e))},writable:!0}),Uint8Array.prototype.forEach||Object.defineProperty(Uint8Array.prototype,"forEach",{value:Array.prototype.forEach,writable:!0}),n})(),"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.signalR=e():t.signalR=e(),$.extend($t,{t1:"Eingabe erforderlich",t2:"Bitte überprüfen Sie Ihre Eingaben im Formular.",b0:"Erstellt",b1:"Zuletzt geändert",b2:"von",t12:"Der Server hat einen Fehler zurückgegeben. Bitte versuchen Sie es erneut.",t17:"Eine Email mit einem Aktivierungs-Link wurde an deine Adresse versandt.",t18:"Ein Account mit deinem Namen existiert bereits. Dennoch erstellen?",t19:"Einträge sind entweder unngültig oder zu kurz.",t20:"Der Server hat einen Fehler gemeldet. Bitte versuch es erneut.",t21:"Der Zugang wurde nicht gefunden.",t30a:"Als erledigt markieren.",t30b:"Als unerledigt markieren.",t55:"Ein Email mit einem Aktivierungs-Link wurde an Ihre Adresse versandt.",t56:"Ein Zugang für diesen Namen besteht bereits. Trotzdem erstellen?",t57:"Ein bestehender Zugang wurde für diese Serie registriert.",t60:"Bitte geben Sie Email-Adresse an, die Sie hier hinterlegt haben.",t61:"Ihr Passwort wurde erfolgreich versandt.",t62:"Die angegebene Email-Adresse stimmt nicht mit der hier hinterlegten überein.",ov:"Persönliche Übersicht"});var $v={}; /*! loadCSS. [c]2020 Filament Group, Inc. MIT License */ /*! onloadCSS. (onload callback for loadCSS) [c]2017 Filament Group, Inc. MIT License */ -function onloadCSS(t,e){e=e||{};let n=function(e){return new Promise(((n,i)=>{t.addEventListener?e.addEventListener("load",newcb):t.attachEvent&&e.attachEvent("onload",newcb),"isApplicationInstalled"in navigator&&"onloadcssdefined"in t&&e.onloadcssdefined(newcb)}))};if(Array.isArray(t)){let i=t.length;Promise.all(t.map(n)).then((function(t){var n=t.reduce(((t,e)=>t+(!0===e?1:0)));!async function(t){!0===t&&"function"==typeof e.success?e.success():!0===t&&"object"==typeof e.success&&e.success instanceof Promise&&await e.success(),e.complete()}(i===n)}))}else n(t)}!function(t){"use strict";var e=function(e,n,i,o){var r,s=t.document,a=s.createElement("link");if(n)r=n;else{var c=(s.body||s.getElementsByTagName("head")[0]).childNodes;r=c[c.length-1]}var l=s.styleSheets;if(o)for(var u in o)o.hasOwnProperty(u)&&a.setAttribute(u,o[u]);a.rel="stylesheet",a.href=e,a.media="only x",function t(e){if(s.body)return e();setTimeout((function(){t(e)}))}((function(){r.parentNode.insertBefore(a,n?r:r.nextSibling)}));var d=function(t){for(var e=a.href,n=l.length;n--;)if(l[n].href===e)return t();setTimeout((function(){d(t)}))};function h(){a.addEventListener&&a.removeEventListener("load",h),a.media=i||"all"}return a.addEventListener&&a.addEventListener("load",h),a.onloadcssdefined=d,d(h),a};"undefined"!=typeof exports?exports.loadCSS=e:t.loadCSS=e}("undefined"!=typeof global?global:this);const isIE=/MSIE\/|Trident/gi.test(window.navigator.userAgent)||void 0!==window.document.documentMode,isfileapi=!!(window.File&&window.FileReader&&window.FileList&&window.Blob);var $ocms={auth:{},no:function(t){t.stopPropagation()},vmin:function(t){var e=$(window).width*(t||1),n=$(window).height*(t||1);return e($ocms.baseurl+"/"+(t||"")).replace(/\/\//,"/"),cexi:null};function deepCopy(t){var e,n,i;if("object"!=typeof t||null===t)return t;for(i in e=Array.isArray(t)?[]:{},t)n=t[i],e[i]=deepCopy(n);return e}function fields_definition(t,e,n){this.label_sng=!0===Array.isArray(t)?"":t||"",this.label_pl=!0===Array.isArray(t)?"":e||"",this.fields=!0===Array.isArray(t)?t:n||[],this.itm=function(t){for(var e=0;e0)for(var n=0;nt||"")).filter(((t,e)=>""!==t)).join(e)}function parseDt(t,e,n){t=(t||"").substr(0,e.length);var i=e,o=t.length>0&&e.split(";").some((function(e){for(var n,o=/[^yMdhms0-9]/gi,r=!0;null!==(n=o.exec(e));)r=r&&e.substr(n.index,1)===t.substr(n.index,1);var s=t.length===e.length&&r;return!0===s&&(i=e),s}));if(!0===o){for(var r,s=[0,0,0,0,0,0,0],a=/(mm{1,2}|dd{1,2}|MM{1,2}|(yy){2,4}|ss{1,2}|hh{1,2})(?!\w)/g;null!==(r=a.exec(i));)s["yMdhms".indexOf(r[0].substr(0,1))]=parseInt(("yy"===r[0]?"20":"")+t.substr(r.index,r[0].length))-("M"===r[0].substr(0,1)?1:0);var c=new(Function.prototype.bind.apply(Date,[null].concat(s)));return"string"==typeof n?fdt(c,n):c}return!1}function bool(t,e){return"boolean"==typeof t?t:"boolean"==typeof e&&e}function booln(t,e){return"boolean"==typeof t?t:"number"==typeof t?1===t:"boolean"==typeof e&&e}Date.prototype.isValid=function(){return!isNaN(this)},Date.prototype.format=function(t){return fdt(this,t)},Date.prototype.addDays=function(t){return this.setDate(this.getDate()+t),this},Date.prototype.isBetween=function(t,e){return this>t&&this section");$(window).scroll((function(e){let n=$(window).scrollTop(),i=$("body");i.toggleClass("unfocus",n>vh()-1.2*t),i.toggleClass("btb",n>.5*vh()-t)}))},$ocms.cf_reset=function(){return $("#contentframe").empty()},function(t){t.fn.scrollTo=function(e){if(t(this).length>0){var n=t(this).offset().top||0;n>0&&t("html, body").animate({scrollTop:n-hh()},2e3)}},t.fn.ldng=function(e){var n=!0;return"boolean"==typeof e?n=e:"number"==typeof e&&(n=e>0),t(this).toggleClass("loading",n)},"function"!=typeof t.noop&&(t.noop=function(){}),t.fn.hasAttr=function(e){var n=t(this).attr(e);return void 0!==n&&!1!==n},t.fn.parseCssPx=function(e){try{return parseFloat(t(this).css(e).replace("px","")||0)}catch(t){return 0}},t.max=function(t,e){return isNaN(t)&&isNaN(e)?null:isNaN(t)&&!isNaN(e)?e:!isNaN(e)&&isNaN(e)||t>=e?t:e},t.min=function(t,e){return isNaN(t)&&isNaN(e)?null:isNaN(t)&&!isNaN(e)?e:!isNaN(e)&&isNaN(e)||t<=e?t:e},t.lim=function(t,e){return isNaN(t)?null:isNaN(e)?t:e<=t?e:t},t.fn.enterKey=function(e){return this.each((function(){t(this).keypress((function(t){"13"===(t.keyCode?t.keyCode:t.which).toString()&&e.call(this,t)}))}))}}(jQuery),$ocms.defaultTimeout=3e4,$ocms.AjaxEX=function(t){var e=this;e.responseText=e.responseText||"";var n=e.getResponseHeader("x-ocms-code")||"";e.internalCode=""!==n&&!1===isNaN(n)?parseInt(n):-1,e.isInternal=e.internalCode>-1,e.internalText=decodeURIComponent((e.getResponseHeader("x-ocms-desc")||"").replace(/\+/g,"%20")||"");var i=e.internalText||t,o=e.internalCode||e.status;e.logtext=i+" ("+o+")"},$ocms.postXTS=function(t){$ocms.postXT.call(this,$.extend(t,{sync:!0}))},$ocms.postXT=function(t){if((t=t||{}).trycount=t.trycount||0,""!==(t.url||"")){t.url=-1!==t.url.indexOf("&yy=")?t.url:t.url.indexOf("?")>-1?t.url+"&yy="+(new Date).getTime():t.url+"?yy="+(new Date).getTime();var e=t.context||this;switch(t.context=e,t.retryLimit=t.retryLimit||0,t.timeout=t.timeout||$ocms.defaultTimeout,t.timeout<100&&(t.timeout=1e3*t.timeout),t.data=t.data||{},t.contentType=t.contentType||"multipart/form-data; charset=UTF-8",t.islogin="boolean"==typeof t.islogin&&t.islogin,t.contentType){case"":case"json":t.contentType="application/json; charset=utf-8";break;case"form":t.contentType="application/x-www-form-urlencoded; charset=UTF-8";break;case"multi":t.contentType="multipart/form-data";break;case"text":t.contentType="text/plain; charset=UTF-8"}if(t.form instanceof jQuery?(t.data=t.form.serializeObject(),t.contentType="form-data"):t.lzw instanceof jQuery&&(t.data.lzw=$.ccLZW(t.lzw.serializeAnything(!0)).join(",")),"multipart/form-data"!==t.contentType.substr(0,19)&&"form-data"!==t.contentType.substr(0,9)||t.data instanceof FormData!=!1)t.data instanceof FormData&&(t.contentType=!1,t.processData=!1);else{t.contentType=!1;var n=new FormData;$.each(t.files||[],(function(t,e){n.append("upload_file",e)})),$.each(t.data||{},(function(t,e){n.append(t,e)})),t.data=n,t.processData=!1}var i={type:t.method||"post",url:t.url,data:t.data,processData:"boolean"!=typeof t.processData||t.processData,contentType:t.contentType,cache:t.cache||!1,timeout:t.timeout,beforeSend:function(n){$(t.loading).ldng(),$("body").addClass("ldng"),"function"==typeof t.beforesend&&t.beforesend.apply(e,[n])},success:function(n,i,o){"false"===n||"not authorized"===n?("function"==typeof t.error&&t.error.apply(e,[o,i,n]),"function"==typeof $.status&&$.status(i+" - "+n)):"function"==typeof t.success&&t.success.apply(e,[n,i,o])},error:function(n,i,o){if($ocms.AjaxEX.call(n,i),-1===t.url.indexOf("doc.ashx")||-1!==t.url.indexOf("ftest")){if(401===n.status&&111===n.internalCode&&!1===t.islogin&&"function"==typeof $ocms.login.dlg)$ocms.login.dlg({ajo:t});else if("timeout"===i||302===n.status)return t.tryCount++,t.tryCount<=t.retryLimit?void $ocms.postXT(t):void 0;"function"==typeof t.error?t.error.apply(e,[n,i,o]):"function"==typeof $ocms.failure?$ocms.failure.apply(e,[n]):"function"==typeof $.status&&$.status("Server error: "+i+" - "+o)}},dataType:t.datatype||"json",complete:function(n,i){"function"==typeof t.complete&&t.complete.apply(e,[n,i]),$(t.loading).ldng(0),$("body").removeClass("ldng");let o=$("body > .timer");if(o.length>0){let t=new Date(n.getResponseHeader("ocms_cec")||""),e=new Date(n.getResponseHeader("ocms_cex")||"");if(t.isValid()&&e.isValid()){let n=new Date,i=Math.abs(e-t);n.setMilliseconds(n.getMilliseconds()+i),o.data({cex:n,ctt:i}),$ocms.cex_timer()}}},context:e,async:!0};return"boolean"==typeof t.sync&&(i.async=!1===t.sync),!0==("boolean"==typeof t.contentType&&!1===t.contentType)&&(i.contentType=!1),$.ajax(i)}},$ocms.cex_timer=function(){$ocms.cexi||($ocms.cexi=setInterval($ocms.cex_timer,15e3));let t=$("body > .timer"),e=t.data("cex"),n=t.data("ctt"),i=new Date;if(e instanceof Date&&e.isValid()&&"number"==typeof n&&n>0&&e>i){let o=Math.abs(i-e)/n*100;t.css("width",o.toString()+"%"),o<98&&(!$ocms.cex_lp||Math.abs(i-$ocms.cex_lp)>6e5)&&$ocms.postXT({url:$ocms.url("ping"),success:()=>{$ocms.cex_lp=i},timeout:5e3,error:()=>{}})}},$ocms.vbl_send=function(t){var e=t.data||{};if(""!==(e.url||"")){var n=$("#contentframe form:first"),i={url:e.url,data:new FormData,success:function(t){"function"==typeof e.success?e.success(t):"string"==typeof e.success&&alert(e.success)},error:function(t,n,i){"function"==typeof e.error?e.error(i):"string"==typeof e.error&&alert(e.error)},complete:function(){n.ldng(0)}},o=!0;n.find("input").each((function(){var t=$(this),e=t.nza("name"),n=t.val(),r=$(this).prop("required")||!1;if(""!==e){var s=""!==n||!1===r;o=o&&s,!0===s?(i.data.append(e,n),t[0].setCustomValidity("")):""!==$(this).nza("ocms-nvnote")&&t[0].setCustomValidity($(this).nza("ocms-nvnote"))}})),!0===o&&(n.ldng(1),$ocms.postXT.call(this,i))}},function(t){t.fn.nza=function(e,n){var i=t(this).attr(e);return void 0!==i&&!1!==i?i:n||""},t.fn.serializeObject=function(e,n){var i=/\r?\n/g,o=/^(?:submit|button|image|reset|file)$/i,r=/^(?:input|select|textarea|keygen)/i,s=/^(?:checkbox|radio)$/i,a=bool((n=n||{}).typedvalues,!1),c={},l=t(this),u=l.find(':input:not([nosend],[type="file"])').addBack(":input"),d=!0;return t.each(u.not(".tinymce").get(),(function(n,l){var u=t(this),h=this,p=(this.type||"").toLowerCase(),f=u.prop("required")||!1;if(!0===(h.name&&!u.is(":disabled")&&r.test(h.nodeName)&&!o.test(p))){var m=u.val(),g=h.name,y=u.nza("data-format").split(":"),v=u.nza("pattern")||".*";if(!0===s.test(p)&&(m=h.checked?""!==m?m:"true":""),"date"===y[0].substr(0,4)&&y.length>1)"boolean"==typeof(m=parseDt(m,y.slice(1).join(":")))&&(m=null),null===m&&"date"===u.prop("type").substr(0,4)&&!1===isNaN(new Date(u.val()))&&(m=new Date(u.val())),m instanceof Date==!0&&"function"==typeof m.getMonth?!1===a&&(m=fdt(m,"date"===y[0]?"dts":"iso")):m=null;else if(("number"===p||"float"===y[0]||"integer"===y[0])&&!0===a){let t,e=String(m).replace(",",".");t="integer"===y[0]?parseInt(e):parseFloat(e),m=isNaN(t)?m:t}if(!0===f&&(""===m||null==m||"string"==typeof m&&null===m.match(v))?(!0===bool(e,!1)&&h.setCustomValidity(u.nza("ocms-nvnote",$ocms.t.inv||"Invalid field")),m=null):!0===bool(e,!1)&&h.setCustomValidity(""),null!=m&&"string"==typeof m){let t=c[g];null!=t?Array.isArray(t)?t.push(m.replace(i,"\r\n")):c[g]=[t,m.replace(i,"\r\n")]:c[g]=m.replace(i,"\r\n")}else if(null!=m){let t=c[g];null!=t?Array.isArray(t)?t.push(m):c[g]=[t,m]:c[g]=m}else d=!1}})),u.filter(".tinymce").each((function(e,n){var i=t(this),o=((this.type||"").toLowerCase(),i.prop("required")||!1);try{var r=tinymce.get(t(n).attr("id"));if(r){var s=t(n).attr("name"),a=r.getContent();!1===o||""!==(a||"")?c[s]=a:d=!1}}catch(e){t.noop()}})),l.toggleClass("invalid",!d),d?c:null},t.fn.sendForm=function(e,n,i){var o=t(this);i=i||{};var r={url:e,success:function(t){if(i.response=t,"function"==typeof n)n(t);o.closest("div.modal").remove()},error:function(t,e,n){"function"==typeof i.error?i.error.call(this,t):$ocms.failure.call(this,t)},complete:function(){o.ldng(0),"function"==typeof i.complete&&i.complete.call(this,jqXHR)}},s=o.find('input[type="file"]');r.data=new FormData,s.length>0&&t.each(s[0].files,(function(t,e){r.data.append(t,e),r.data.append("file_lastmodified",$ocms.isodt(e.lastModifiedDate))}));var a=o.serializeObject();t.each(a||{},(function(t,e){r.data.append(t,e)})),o.ldng(),$ocms.postXT.call(this,r)},t.fn.checkValidity=function(){var e=t(this),n=!0;return e.each((function(t,e){n=n&&e.checkValidity()})),n},t.fn.wrap=function(e,n){var i=t(this),o=$$.dc(e).attr(n||{}).insertAfter(i);return i.append(o),o}}(jQuery),$ocms.logout=function(){$ocms.postXT({url:$ocms.url("logout"),complete:function(){window.location.reload()}})},$ocms.login={send:function(t){t.preventDefault();var e=$(this);if(!0===e.find("#dbtn-confirm").hasClass("disabled"))return!1;var n=e.serializeObject();return n.loginaccount=ne(n.loginaccount,$ocms.auth.account||$ocms.auth.requestedaccount||""),n.loginaccount=ne(n.loginaccount,$ocms.auth.account||$ocms.auth.requestedaccount||""),""===ne(n.loginaccount)&&!0===bool($ocms.auth.accountrequired,!0)?(alert($t.l16),!1):($ocms.postXT({url:$ocms.url("login"),data:n,success:function(){window.location.reload()}}),!1)},uichange:function(){let t=$(this),e=t.closest("form"),n=bool($ocms.auth.accountrequired,!0),i=ne(e.find('[name="loginaccount"]').val(),$ocms.auth.account||$ocms.auth.requestedaccount||"");if(""!==i||!1===n){var o=e.find('[name="userlogin"]').empty().val(""),r=e.find('[name="username"]').empty().val(""),s=$("#dlg_userlogin_sel").empty().val(""),a=t.val()||"";if(!1===t.checkValidity()&&""===a)return;var c=t.closest("table").ldng();$ocms.postXT.call(this,{url:$ocms.url("auth"),data:{userinfo:a,account:i||""},success:function(t,e,n){if(1===t.length){var i=t[0];o.val(i.login).change().attr("required","").removeAttr("nosend"),r.val(i.name).change().attr("required","").show(),s.removeAttr("required").attr("nosend","").hide()}else t.length>0?(r.hide().removeAttr("required"),o.removeAttr("required").attr("nosend",""),0===s.length&&(s=$("").attr({name:"userlogin",size:t.length,id:"dlg_userlogin_sel",class:"form-control",required:""}).css({width:"100%","max-width":"100%",padding:"2px"}).insertAfter(r)),$.each(t,(function(t,e){var n=$("").attr({value:e.login,style:"padding-top: 2px; padding-bottom: 5px;","border-bottom":"1px solid #EEE;"}).text(e.name).appendTo(s);t%2==0&&n.css({"background-color":"#F9F9F9"})})),s.attr("required","").removeAttr("nosend")):(s.hide().attr("nosend",""),r.attr("required","").show(),o.attr("required","").removeAttr("nosend"),alert($t.l9))},error:function(t){$ocms.failure.call(this,t)},complete:function(){c.ldng(0)}})}else alert($t.l18)},sendpassword:function(t){var e=$(''),n=e.find(".form-body"),i=null;e.find("form").submit((function(t){t.preventDefault();var o=$(this).serializeObject(!0),r=null===i,s=r?"spwc":"spw";return $ocms.postXT.call(this,{url:$ocms.url(s),data:o,complete:function(){r?(n.append('
Ihnen wurde ein Code per SMS zugesandt.
Bitte tragen Sie den hier ein:
'),i=$('
').appendTo(n)):(alert($t.l13),e.remove())},error:()=>{}}),!1})),e.find(".modal-close").click((function(){e.remove()}));var o=[];$.each($t.l7a.split("\n"),((t,e)=>{Array.prototype.push.apply(o,[$("
"),$("").text(e)])})),e.find(".modal-note").append($('').text($t.alert)).append(o),e.appendTo("body"),setTimeout((function(){$(".modal").find('input[name="lastname"]').focus()}),600)}};var $$={s:function(t){return $("").text(t)},br:function(){return $("
")},sc:function(t,e){return $("").addClass(t).text(e)},td:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},th:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},tdc:function(t,e,n){return $$.td(e,n).addClass(t)},td2:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},td3:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},tdtr:function(t,e){var n=$$.tr().appendTo(e);return t instanceof jQuery==!0||"string"==typeof t?t.appendTo($$.td().appendTo(n)):!0===Array.isArray(t)&&$.each(t,(function(t,e){$(e).appendTo($$.td().appendTo(n))})),n},tr:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t&&n.attr(t),"object"==typeof e&&n.attr(e),n},trc:function(t,e){var n=$("").addClass(t);return e instanceof jQuery==!0?n.appendTo(e):"object"==typeof e&&n.attr(e),n},d:function(t){return $("
").attr(t||{})},dc:function(t,e,n,i){var o=$("
").addClass(t);return e instanceof jQuery==!0?o.appendTo(e):"object"==typeof e?o.attr(e):"function"==typeof e?o.click(e):"string"==typeof e&&o.text(e),"string"==typeof n?o.text(n):"object"==typeof n?o.attr(n):"function"==typeof n&&o.click(n),"string"==typeof i?o.text(i):"object"==typeof i?o.attr(i):"function"==typeof i&&o.click(i),o},df:function(t){return $("
 
").attr(t||{})},opt:function(t,e,n){var i=$("");return"string"==typeof t?i.attr("value",t):"object"==typeof t&&i.attr(t),"string"==typeof e?i.text(e):"object"==typeof e&&i.attr(e),"object"==typeof n&&i.attr(n),i},eOpt:function(t){var e=$('');return t&&e.attr("selected","selected"),e},tbl:function(t){return $("
").attr(t||{})},tblc:function(t){return $("
").addClass(t)},thead:function(t){let e=$("");return t instanceof jQuery&&e.prependTo(t),e},tbody:function(t){let e=$("");return t instanceof jQuery&&e.appendTo(t),e},tblset:function(t,e){let n=$$.tbl(t||{});return e instanceof jQuery&&e.append(n),{tbl:n,hd:$$.thead().appendTo(n),bdy:$$.tbody().appendTo(n)}},i:function(t){return $("").attr(t||{})},img:function(t,e){return $("").attr("src",t).attr(e||{})},sel:function(t){return $("").attr(t||{})},btn:function(t){return $("").attr(t||{})},a:function(t){return $("").attr(t||{})},li:function(t){return $("
  • ").attr(t||{})},ul:function(t){return $("
      ").attr(t||{})},nav:function(t){return $("").attr(t||{})},lbl:function(t,e){var n=$("");return"string"==typeof t&&n.text(t),"object"==typeof t?n.attr(t):"object"==typeof e&&n.attr(e),n},txt:function(t){return $("").attr(t||{})},0:function(t,e){return $("<"+t+">").attr(e||{})},bbtn:function(t,e){return $$.btn({type:"button",class:"btn"}).addClass(e).text(t)},svg:t=>$(document.createElementNS("http://www.w3.org/2000/svg",t))};function getMonday(t){var e=(t=new Date(t)).getDay(),n=t.getDate()-e+(0==e?-6:1);return new Date(t.setDate(n))}function $lf(t){var e=void 0===t?null:"number"==typeof t&&1!==t||"boolean"==typeof cl&&!1===t;return $("#listframe").tC("hd",e).is(".hd")}function $nuf(t){if(t&&t.stopPropagation(),!$(this).is(".disabled")){var e=function(t){t.removeClass("vis").find("li.dropdown").removeClass("open").removeClass("vis").attr("aria-expanded","false")},n=$(this).parent("li.dropdown");if(n.length>0){n.tC("open"),navs=!0===n.is(".open")?"true":"false",n.attr("aria-expanded",navs);var i=n.closest("nav");i.find("li.dropdown").not(n.parentsUntil("nav")).not(n).removeClass("open").attr("aria-expanded","false"),!1===n.is(".open")&&n.find("li.dropdown").removeClass("open").attr("aria-expanded","false"),e($("nav").not(i))}else e($("nav"))}}function $tbr(){return $lf(0),$("#topbar").ocmsmenu([])}function $lfr(){return $("#sidebar").empty(),$("#listframe").removeClass("fix").addClass("hd").empty()}function $cfr(){return $tbr(),$("#contentframe").empty()}function jObj(t,e){let n={};if("{"===(t||"").substr(0,1))try{n=JSON.parse(t)}catch(t){n={}}return n[e]||""}function string(t,e){var n,i=t||"";return $.each(e||[],(function(t,e){n=new RegExp("\\{"+t.toString()+"\\}","ig"),i=i.replace(n,e)})),i}function init_tooltip(t){var e=!0===("boolean"==typeof t&&t)&&"mouse";$("[title]").qtip({position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1}),$("div.tooltiptext").each((function(){$(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:$(this).clone()},position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden}})}))}String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")},String.prototype.left=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.slice(0,e):""}return this.substring(0,t)},String.prototype.right=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.substring(this.length-e):""}return this.substring(this.length-t)},Array.prototype.move=function(t,e){if(e>=this.length)for(var n=e-this.length;1+n--;)this.push(void 0);return this.splice(e,0,this.splice(t,1)[0]),this},function(t){t.fn.appendToIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.appendTo(e),i},t.fn.appendIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.append(e),i},t.fn.rwText=function(e,n,i){var o=t(this).empty();i=t.extend({wrap:!0},i);var r=!0===Array.isArray(e)?e:(null==e?"":String(e)).split("\n");return t.each(r,(function(t,e){""!==(e||"")&&(t>0&&o.append($$.br()),o.append(!0===i.wrap?$$.s(e):e))})),n&&o.attr("title",n),o},t.fn.loadSel=function(e,n,i){if("SELECT"===t(this).prop("tagName").toUpperCase()){var o=t(this);$ocms.postXT.call(this,{url:e,data:n||{},success:function(e){t.each(e,(function(){o.append($$.opt(e.value,e.text))}))},complete:function(){o.ldng(0),"function"==typeof i&&i.call(o)}})}},t.fn.emptyWithEditors=function(e){var n=t(this);return n.find(":input.tinymce").each((function(e,n){try{var i=tinymce.get(t(n).attr("id"));i&&i.remove()}catch(e){t.noop()}})),n.empty()},t.fn.cssValue=function(t){if(this.length>0){var e=this.css(t)||"";if(""===e)return 0;var n=/(^[\d\.]*)(\D{1,3}$)/gi.exec(e);return null!==n?"rem"===n[2]?$ocms.rpx(parseFloat(n[1])):parseFloat(n[1]):!1===isNaN(e)?parseFloat(e):0}return 0},t.fn.veryInnerHeight=function(){let e=e=>t(this).cssValue(e);return t(this).innerHeight()-e("padding-top")-e("padding-bottom")},t.fn.veryInnerWidth=function(){let e=e=>t(this).cssValue(e);return t(this).innerWidth()-e("padding-left")-e("padding-right")},t.fn.marginWidth=function(){let e=e=>t(this).cssValue(e);return e("margin-left")+e("margin-right")},t.fn.marginHeight=function(){let e=e=>t(this).cssValue(e);return e("margin-top")+e("margin-bottom")},t.inArrayRegEx=function(e,n,i){var o="regexp"===t.type(e)?e:new RegExp(e);if(!n)return-1;for(var r=i=i||0;r7){i=e.split(","),o=(n||(t<0?"rgb(0,0,0)":"rgb(255,255,255)")).split(",");var c=a(i[0].slice(4)),l=a(i[1]),u=a(i[2]);return"rgb("+(s((a(o[0].slice(4))-c)*r)+c)+","+(s((a(o[1])-l)*r)+l)+","+(s((a(o[2])-u)*r)+u)+")"}var d=(i=a(e.slice(1),16))>>16,h=i>>8&255,p=255&i;return"#"+(16777216+65536*(s((((o=a((n||(t<0?"#000000":"#FFFFFF")).slice(1),16))>>16)-d)*r)+d)+256*(s(((o>>8&255)-h)*r)+h)+(s(((255&o)-p)*r)+p)).toString(16).slice(1)},t.fn.IN=function(e){return t(this).fadeIn(400,e),t(this)},t.fn.OUT=function(e){return t(this).fadeOut(400,e),t(this)},t.fn.tooltip=function(e,n){var i=!0===("boolean"==typeof e&&e)&&"mouse",o="boolean"==typeof n&&n,r=t(this);return r.each((function(){var e=o?t(this).find(".tooltiptext"):t(this).children(".tooltiptext");t(e).length>0?e.each((function(){var e=t(this);t(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:e.clone()},position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},show:{effect:!1},hide:{effect:!1}}),e.remove()})):t(this).qtip({position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1})})),r},t.fn.rC=function(e){return t(this).removeClass(e)},t.fn.aC=function(e){return t(this).addClass(e)},t.fn.tC=function(e,n){return t(this).toggleClass(e,n)}}(jQuery),function(t){t.fn.ocmsmenu=function(e,n){var i=t(this);return $ocms.menu.call(i,e,n),i},t.fn.activatemenu=function(){var e=t(this).filter("nav");return e.find("a").not(".on").addClass("on").click($nuf),e.find(".nav-btn").not(".on").addClass("on").click((function(e){e.stopPropagation();var n=t(this);t(n.attr("data-target")).tC(n.attr("data-toggle"))})),e}}(jQuery);class ObjectArray extends Array{isEmpty(){return 0===this[0].length}static get[Symbol.species](){return Array}filter(t){return"function"==typeof t?new ObjectArray(this[0].filter(t)):this}remove(t){if("function"!=typeof t)return this;{let e=this[0].findIndex(t);for(;e>-1;)this[0].splice(e),e=this[0].findIndex(t)}}sortBy(t){return"function"==typeof t&&this[0].sort(t),this}sortString(t){return this[0].sort(((e,n)=>{let i=(e[t]||"").toString().toUpperCase(),o=(n[t]||"").toString().toUpperCase();return console.debug(i.localeCompare(o)),i.localeCompare(o)})),this}sortNum(t){return this[0].sort(((e,n)=>{let i=e[t],o=n[t];return!0===isNaN(o)&&!1===isNaN(i)||io?1:0})),this}sum(t){return this[0].reduce(((e,n)=>e+(!0===isNaN(n[t])?0:n[t])),0)}groupBy(t){return this[0].reduce((function(e,n){let i=n[t];return e[i]||(e[i]=[]),e[i].push(n),e}),{})}each(t){if("function"==typeof t){let e=!1;this[0].forEach(((n,i,o)=>{if(!1===e){let r=t(n,i,o);"boolean"==typeof r&&!1===r&&(e=!0)}}))}}get toArray(){return this[0]}}class NumArray extends Array{sum(){return this.reduce(((t,e)=>t+e))}first(){return this[0]}last(){return this[this.length-1]}average(){return this.sum()/this.length}range(){let t=this.map((t=>t)).sort();return{min:t[0],max:t[this.length-1]}}static get[Symbol.species](){return Array}}$ocms.ocmsmenu=[{lbl:"",id:"m_home",ico:"glyphicon glyphicon-home",fnc:"init:home"},{fnc:"separator"}],function(t){t.multline=function(t){let e=t.split("\n"),n=$$.d();return $.each(e,((t,e)=>{n.append($$.s(e))})),n.html()},t.tooltip_hidden=function(t,e){$(this).remove(),e.rendered=!1},t.isJSONDateString=function(t){return"string"==typeof t&&/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/.test(t)},t.failure=function(e){11110===(e.internalCode||-1)?t.login.dlg():alert($t.f1+"\n"+(e.internalText||""))},t.getScript=function(e,n){var i=[],o=[],r=function(t){return"string"==typeof t&&""!==(t||"")},s=function(t,e){!0===bool(e.condition,!0)&&(""!==(e.script||"")&&o.push({url:e.script,module:e.module||""}),!0===r(e.css||"")?i.push(e.css):!0===Array.isArray(e.css)&&Array.prototype.push.apply(i,e.css.filter(r)))};!0===r(e||"")?o.push(e):!0===Array.isArray(e)?$.each(e,s):"object"==typeof e&&""!==(e.script||"")&&s(0,e);let a=[];$.each(i,(function(t,e){""!==(e||"")&&a.push(loadCSS(e))}));let c=o.map((function(e,n){let i=e.url,r=e.module||"";if(""===r){let t=new Promise((function(t,e){try{!async function(){$.ajax({url:i,dataType:"script",success:function(){t(o)},error:function(){e(o)},timeout:3e4})}()}catch(t){console.debug(t.message+"%o",t)}}));return t}return t.loadmodule(r,i,e.alias)}));Promise.all(c).then(n)},t.loadmodule=function(e,n,i){let o=new Promise((function(o,r){!async function(){try{let s=(n.startsWith("/")||n.startsWith(".")?"":"/")+n;import(s).then((n=>{t[e]=n[i||"default"],o(e)})).catch((t=>{console.debug(t.message+"%o",t),r(e)}))}catch(t){console.debug(t.message+"%o",t)}}()}));return o},t.ocms_auth=function(e,n,i,o){!1===$.isPlainObject(t.auth.modules)&&(t.auth.modules={});var r=0;t.auth.modules[e+(i||"")]?((r=t.auth.modules[e+(i||"")])<2&&(i||"")===auth.guid&&(r=2),r>=(n||0)&&o(false)):t.postXT({url:t.url("auth"),data:{module:e,person_guid:i||""},success:function(s){r=s[e],t.auth.modules[e+(i||"")]=r,r<2&&(i||"")===t.auth.person_guid&&(r=2),r>=(n||0)&&o(false)},error:function(e){t.failure.call(this,e)}})},t.auth.locale="de",t.ocms_prepauth=function(e,n,i){t.postXT({url:t.url("auth"),data:{fn:"csv",modules:e,person_guid:n||""},success:function(e){t.ocms_regauth(e)},error:function(e){t.failure.call(this,e)},complete:function(){i()}})},t.ocms_regauth=function(t){$.each(t||{},(function(t,e){auth.modules[t]=parseInt(e)}))},t.init=function(e){var n="string"==typeof e?e:(e.data||{}).fn||"";""!==n&&("home"===n?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),t.ov.call($("#contentframe"))):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),t.postXT({url:t.url(n+"/auth"),success:function(e){void 0===t[n]&&(t[n]={}),t[n].auth=e,e.manage>0&&t.getScript({module:n,script:["web/imdl",n,t.auth.locale||"de","js"].join("."),css:["web/imdl",n,"css"].join("."),condition:"function"!=typeof t[n].init2},(function(){t[n].init2()}))},error:function(){$("#contentframe").empty()}})))},t.menuarray=function(t){this.array=[],this.sep=function(){this.length>0&&"separator"!==this.array[array.length-1].fnc&&this.push({fnc:"separator"})},this.push=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.push.apply(this.array,t):"object"==typeof t&&this.array.push(t),t)},this.unshift=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.unshift.apply(this.array,t):"object"==typeof t&&this.array.unshift(t),t)},this.push(t)},t.menu=function(e,n){e=e||[];var i=$(this).removeClass("vis");if(!0===bool(n,!0)&&!1===i.is("#mainmenu")&&i.empty(),!1===bool(n,!1)&&i.is("#sidebar,#topbar")&&(e.unshift({id:"sbctrl",glyph:"glyphicon-th-list",aclass:"fbtn",fnc:function(){$lf()}}),$lf(0)),0===(e||[]).length)i.empty().addClass("hd");else{i.removeClass("hd");var o=!0===i.is("nav")?i:i.children("nav");1!==o.length&&(o=$("").tC("nv",i.is("#sidebar")).tC("ctxt",i.is("#topbar")).appendTo(i));var r,s=$$.ul().appendTo(o),a=function(t,e){var n=$(this).addClass("dropdown submenu");t.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"}),""!==(e.ico||"")&&t.prepend($$.sc("ico "+e.ico));var i=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(n);$.each(e.itm||[],(function(t,e){r.call(i,e)}))},c=function(t){$(this).tC("disabled","boolean"==typeof t.disabled?t.disabled:"string"==typeof t.disabled&&"subs"===t.disabled&&0===(t.itm||[]).length)};r=function(e){var n,i=$$.li({id:e.id}).attr(e.attr||{}).addClass(e.lclass).appendTo($(this)),o="string"==typeof e.fnc&&""!==e.fnc?e.fnc.split(":")[0]:"";""!==o&&"init"!==o?i.attr("role",o).appendIf($$.s(e.lbl),""!==ne(e.lbl)):(n=$$.a({class:"on",role:"button"}).addClass(e.aclass).appendTo(i).append($$.s(e.lbl)),c.call(n,e),(e.itm||[]).length>0&&a.call(i,n,e),n.click($nuf),"function"==typeof e.fnc?n.click(e.data||{},e.fnc):"init"===o&&n.click($.extend({},e.data||{},{fn:e.fnc.split(":")[1]}),t.init))},$.each(e,(function(e,n){var i,o=$$.li({id:n.id}).attr(n.attr||{}).addClass(n.lclass),a="string"==typeof n.fnc&&""!==n.fnc?n.fnc.split(":")[0]:"";if(""!==a&&"init"!==a)o.attr("role",a).appendIf($$.s(n.lbl),""!==ne(n.lbl));else{if(i=$$.a({class:"on",role:"button"}).addClass(n.aclass).appendTo(o),c.call(i,n),""!==(n.lbl||"")&&i.append($$.s(n.lbl)),""!==(n.ico||"")&&i.prepend($$.sc("ico "+n.ico)),""!==(n.glyph||"")&&i.prepend($$.sc("glyphicon "+n.glyph)),(n.itm||[]).length>0){o.addClass("dropdown"),i.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"});var l=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(o);$.each(n.itm||[],(function(t,e){r.call(l,e)}))}(n.sel||[]).length>0||(i.click($nuf),"function"==typeof n.fnc?i.click(n.data||{},n.fnc):"init"===a&&i.click($.extend({},n.data||{},{fn:n.fnc.split(":")[1]}),t.init))}o.appendTo(s)})),o.activatemenu()}},t.easytbl=(t,e)=>{e=e||{};let n=$$.tbl().addClass(e.class).css("border-collapse","collapse"),i=($$.tbody(n),!0===bool(e.frame,!1)?{padding:"5px",border:"1px solid #727272"}:{});if(!0===Array.isArray(e.header)){let t=$$.thead(n);$.each(e.header,((n,o)=>$$.th(t).css(e.cellcss||i).rwText(o)))}else if(!0===bool(e.header,!1)&&(t||[]).length>0){let o=$$.thead(n);$.each(Object.keys(t[0]),((t,n)=>$$.th(o).css(e.cellcss||i).rwText(n)))}return $.each(t||[],((t,o)=>{let r=$$.tr();$.each(o,((t,n)=>{n=n||"";let o=$$.td(r).css(e.cellcss||i);n instanceof jQuery?o.append(n):"string"==typeof n&&("<"===n.substring(0,1)?o.append(n):o.text(n))})),n.append(r)})),n},t.dlgtbl=(e,n,i)=>{i=i||{};let o=t.easytbl(e,i);t.dlg(o,$.extend({title:n},i))},t.dlg=function(t,n){n=n||{};let i=$("body > .modal").length>0,o=t=>typeof n[t],r=t=>"function"===o(t);if(!0===bool(n.exclusive,!0)&&!0===i)return void alert($t.dbldlg||"Es ist bereits ein Dialog geöffnet");let s=$$.dc("modal",$("body")),a=$$.dc("modal-dialog",s);!1===isNaN(n.zindex)?s.css("zIndex",n.zindex):!0===i&&s.css("zIndex",parseInt($("body > .modal:last").cssValue("zIndex"))+200),!1===isNaN(n.zindex_min)&&s.cssValue("zIndex")').appendTo(d)),""!==ne(n.title)&&(c=$$.dc("modal-header",d),$("

      ").text(n.title).appendTo(c));let p=$$.dc("modal-body",d),f=$$.dc("modal-footer",d);t instanceof jQuery==!0&&p.append(t);let m=function(t){t&&"function"==typeof t.stopPropagation&&t.stopPropagation(),a.removeClass("in"),!0===r("closing")&&n.closing.call(d),p.hide().emptyWithEditors(),s.remove(),!0===r("close")&&n.close.call(d)};if(d.find(":input[required]").length>0&&($$.dc("note_required",f).append($$.sc("ind_required","*")).append($$.s($t.t1||"Eingabe erforderlich")),$$.dc("note_invalid",f).append($$.s($t.t2||"Bitte überprüfen Sie Ihre Eingaben im Formular."))),!0===r("cancel")){$$.bbtn(n.cancelbutton||"Abbrechen","cancel").attr({type:"button",role:"cancel"}).appendTo(f).click((function(t){n.cancel.call(d,t);t.stopPropagation(),m()}))}if(!0===r("confirm")){let t=$$.bbtn(n.button||"OK","confirm").attr({type:!0===bool(n.form,!1)?"submit":"button",role:"confirm"}).appendTo(f);!0===h?(d.submit((function(t){try{n.confirm.call(d,t)}finally{t.preventDefault()}return!1})),d.on("modal_submit",(function(){n.confirm.call(d,e)}))):(t.click((function(t){n.confirm.call(d,t);t.stopPropagation()})),d.on("modal_submit",(function(){t.click()})))}else!0===h&&d.submit((function(t){return t.preventDefault(),!1}));return d.on("modal_close",(function(){m()})),l.click(m),!0===r("opening")&&n.opening.call(d),a.addClass("in"),ne(n.mode).indexOf("maxbody")>-1&&p.css("min-height",(u.height()-c.outerHeight()-f.outerHeight()).toString()+"px"),!0===r("open")&&n.open.call(d),{hd:c,bdy:p,ft:f,ct:u,dlg:a,c:d}},t.mform=function(e){let n=$$.dc("form-body"),i=Array.isArray(e)?e:e instanceof fields_definition?e.fields:[];return $.each(i||[],(function(e,i){let o=i.type||"";if("ignore"===o)return!0;let r=$$.dc("form-group",n),s=i.id||"dlg_"+(i.name||"")+("html"===i.type?"_"+(65536*(1+Math.random())||0).toString(16).substr(9):""),a=$$.lbl(i.label||i.name,{for:s}).appendTo($$.dc("form-itm",r)),c=$$.dc("form-itm",r),l=$$.i({id:s,name:i.name,placeholder:i.placeholder,type:i.type});switch(o){case"email":i.pattern=ne(i.pattern,"[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$");break;case"url":i.pattern=ne(i.pattern,"https?://.+");break;case"number":i.pattern=ne(i.pattern,"[-+]?[0-9]*[.,]?[0-9]*"),l.attr("type","text"),l.attr("inputmode","decimal"),l.attr("step",i.precision||"any"),l.attr("data-format","float");break;case"integer":case"int":i.pattern=ne(i.pattern,"[-+]?[0-9]*"),l.attr("type","number"),l.attr("data-format","integer");break;case"date":if(""!==ne(i.pattern,$t.datepattern)&&(i.pattern=ne(i.pattern,"("+$t.datepattern+")|([0-9]{4}.(0[1-9]|1[012]).(0[1-9]|1[0-9]|2[0-9]|3[01]))")),""!==ne(i.placeholder,$t.dateplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.dateplaceholder)),"string"==typeof i.value){var u=i.value.substr(0,10);i.value="date"!==l.prop("type")?fdt(u+"T00:00:00",ne(i.dateformat,$t.dateformat)):u}l.attr("data-format","date:"+ne(i.dateformat,$t.dateformat)+";yyyy-MM-dd");break;case"datetime":l.attr("type","datetime-local"),""!==ne(i.pattern,$t.datetimepattern)&&(i.pattern=ne(i.pattern,"("+$t.datetimepattern+")|([0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])\\s([0-5][0-9]):([0-5][0-9]))")),""!==ne(i.placeholder,$t.datetimeplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.datetimeplaceholder)),"string"==typeof i.value&&"T"===i.value.substr(10,1)&&(i.value="datetime"!==l.prop("type").substr(0,8)?fdt(i.value,ne(i.datetimeformat,$t.datetimeformat)):i.value),l.attr("data-format","datetime:"+ne(i.datetimeformat,$t.datetimeformat)+";yyyy-MM-dd HH:mm:ss");break;case"hidden":r.addClass("hd");break;case"html":case"text":l=$$.txt({id:s,name:i.name,placeholder:i.placeholder,type:i.type}),l.tC("tinymce","html"===i.type);break;case"bool":case"boolean":i.url=[{value:"true",label:($t||{}).true||"Yes"},{value:"false",label:($t||{}).false||"No"}],"boolean"==typeof i.value&&(i.value=i.value?"true":"false");case"select":l=$$.sel({id:s,name:i.name,type:i.type}),!1===bool(i.required,!1)&&$$.eOpt().appendTo(l);try{var d=function(t){!0===Array.isArray(t)&&$.each(t,(function(t,e){"string"==typeof e?$$.opt(e,e).appendTo(l):!0===Array.isArray(e)?$$.opt(e[0],e[1]).appendTo(l):"object"==typeof e&&$$.opt(e.value,e.label||e.text).appendTo(l)}))};!0===Array.isArray(i.url)?d(i.url):"function"==typeof i.url?i.url.call(l):"string"==typeof i.url&&t.postXT({url:i.url,success:d})}catch(t){$.noop()}break;default:""!==ne(i["max-length"])&&l.attr("max-length",i["max-length"])}""!==ne(i.pattern)&&l.attr("pattern",i.pattern),l.val(i.value).change(),l.change((function(){$(this)[0].setCustomValidity("")})),l.addClass("form-control").prop("required",bool(i.required,!1)).prop("readonly",bool(i.readonly,!1)).appendTo(c),!0===bool(i.required,!1)&&a.append($$.sc("ind_required","*")),"object"==typeof i.attr&&l.attr(i.attr),"object"==typeof i.prop&&l.prop(i.prop),"string"==typeof i.class&&l.addClass(i.class),"function"==typeof i.change&&(l.change(i.change),!0===bool(i.applychange,!1)&&void 0!==i.value&&l.change()),""!==(i.note||"")&&$$.dc("form-note",c).rwText(i.note),"function"==typeof i.complete&&i.complete.call(l)})),n},t.initMCE=function(t,e){t=$(t),e=e||{};try{let n={target:t[0],inline:!1,width:e.width||"100%",statusbar:!1,document_base_url:window.location.origin+"/",content_style:"ph:before {content: '«'; color: #BBB; font-style:italic; } ph:after {content: '»'; color: #BBB; font-style:italic; } ph { color: #AAA; font-style:italic; }",relative_urls:!1,remove_script_host:!1};!0===bool(e.hidemenu,!1)&&(n.menubar=!1,n.menu={}),!0===bool(e.hidetoolbar,!1)&&(n.toolbar=!1),$.extend(n,e||{}),tinymce.init(n)}catch(t){alert(t.message)}},t.dlgform=function(e,n){n=n||{};let i,o=$$.dc("frm").append(t.mform(e||[]).addClass("stacked"));n.addcontent instanceof jQuery&&o.append(n.addcontent),"function"==typeof n.submit?i=n.submit:"function"==typeof n.success&&(i=function(e){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject(bool(n.checkvalidity,!0),{typedvalues:bool(n.typedvalues,!1)}));""!==(n.url||"")?t.postXT({url:n.url,data:o,success:function(t){n.success.call(this,t),i.trigger("modal_close")},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4}):(n.success.call(this,o),i.trigger("modal_close"))});let r={form:!0,title:n.title||"",button:n.button||$t.submit,confirm:i,size:n.size||[500,600],open:function(){let e=$(this).find(".tinymce");e.length>0&&t.initMCE(e,n.tinymce||{})}};return t.dlg.call(this,o,r)},t.login.dlg=function(e){e=e||{};let n=[{name:"userinfo",label:$t.l1,type:"string",value:t.auth.login,change:t.login.uichange,required:!0},{name:"userlogin",type:"hidden",required:!0,value:t.auth.login},{name:"username",type:"string",label:$t.l4,required:!0,readonly:!0,placeholder:$t.l5,value:t.auth.fullname_rev},{name:"userpass",type:"password",label:$t.l3,required:!0,placeholder:$t.l3}];""===(t.auth.account||"")&&n.unshift({id:"dlg_loginaccount",name:"loginaccount",type:"string",required:!0,value:t.auth.account});let i=$$.dc("frm").append(t.mform(n).addClass("stacked")),o=t.dlg.call(this,i,{form:!0,title:$t.l0,button:$t.submit,confirm:function(n){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject());t.postXT({url:"/vt/login",data:o,success:function(n){""!==((n||{}).login||"")&&(i.trigger("modal_close"),t.auth=n,"object"==typeof e.ajo&&(e.ajo.islogin,$.ajax(e.ajo)))},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4})},size:[500,600]}),r=$$.dc("modal-content").css("height","auto").attr("novalidate","true").append($$.dc("modal-header").appendIf($("

      ").text(t.auth.accountname),""!==(t.auth.accountname||"")).append($("

      Vereinsmanager

      ")));o.dlg.prepend(r)},t.addNoEntryInfo=function(t){$(this).append($$.dc("noentryinfo").text(t||$t.t11))}}($ocms),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(t){var e=this;do{if(Element.prototype.matches.call(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}),function(t,e){var n,i;"object"==typeof window&&(window[t]=(n=function(t){var e=window,n=document.body,i=document.documentElement,o=Math.max(0,e.pageXOffset||i.scrollLeft||n.scrollLeft||0)-(i.clientLeft||0),r=Math.max(0,e.pageYOffset||i.scrollTop||n.scrollTop||0)-(i.clientTop||0);return{x:t?Math.max(0,t.pageX||t.clientX||0)-o:0,y:t?Math.max(0,t.pageY||t.clientY||0)-r:0}},(i=function(t,e){t&&t instanceof Element&&(this._container=t,this._options=e||{},this._clickItem=null,this._dragItem=null,this._showDragItem="boolean"!=typeof this._options.dragItem||!1!==this._options.dragItem,this._hovItem=null,this._sortLists=[],this._click={},this._dragging=!1,this._dragHandleClass=this._options.dragHandleClass||"",this._parentident=this._options.parentident||"",this._swapdone="function"==typeof this._options.swapdone?this._options.swapdone:null,this._onend="function"==typeof this._options.onend?this._options.onend:null,this._container.setAttribute("data-is-sortable",1),this._container.classList.add("sortable"),this._container.style.position="static",window.addEventListener("mousedown",this._onPress.bind(this),!0),window.addEventListener("touchstart",this._onPress.bind(this),!0),window.addEventListener("mouseup",this._onRelease.bind(this),!0),window.addEventListener("touchend",this._onRelease.bind(this),!0),window.addEventListener("mousemove",this._onMove.bind(this),!0),window.addEventListener("touchmove",this._onMove.bind(this),!0))}).prototype={constructor:i,toArray:function(t){t=t||"id";for(var e=[],n="",i=0;ii.left&&ei.top&&n0?a.mousedown(c).addClass("dctrl"):s.mousedown(c).addClass("dctrl"),t(this)}}(jQuery),$(document).ready((function(){$("html").click((function(t){$nuf()})),$("#listframe").click((function(t){t.stopPropagation(),$nuf()})),$("#mainmenu").ocmsmenu($ocms.ocmsmenu),$("#mainmenu").activatemenu()})),$.extend($t,{m_inv:"Rechnungen",m_req:"Aufträge",m_rep:"Berichte",m_todo:"ToDos",m_bcd:"BankBuchungen",m_admin:"Administration",rsp:"Passwort ändern",pnm:"Die Passwörter stimmen nicht überein",cps:"Das neue Passwort wurde gespeichert.",pwr:"Bitte wählen Sie ein starkes Passwort (min 8 Zeichen, davon jeweils min 2 Zahlen, kleine und große Buchstaben, Sonderzeichen sind optional).",smsc:"Sie beötigen für diese Funktion einen SMS-Code.\nSoll dieser nun versandt werden?",wdc:"Doppelt klicken, um die Box zu aktualisieren.",wdg:{}}),$t.rspf={sms:"Der SMS-Code konnte nicht bestätigt werden",valid:"Das alte Passwort ist nicht korrekt",requirements:"Das Passwort entspricht nicht den Anforderungen.\n"+$t.pwr},$fd={rsp:new fields_definition("","",[{name:"opw",label:"aktuelles Passwort",type:"password",required:!0,attr:{"auto-complete":"current-password"}},{name:"npw",label:"neues Passwort",type:"password",required:!0,pattern:"(.{6,})",attr:{"auto-complete":"new-password"}},{name:"npwc",label:"neues Passwort (Bestätigung)",type:"password",required:!0,attr:{"auto-complete":"new-password"},note:$t.pwr},{name:"code",label:"SMS-Code",type:"string",required:!0,attr:{"auto-complete":"one-time-code"}}])},$ocms.init=function(t){var e="string"==typeof t?t:(t.data||{}).fn||"";""!==e&&("home"===e?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),$fis.ov()):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),$ocms.postXT({url:$ocms.url(e+"/auth"),success:function(t){void 0===$ocms[e]&&($ocms[e]={}),$ocms[e].auth=t,t.manage>0&&$ocms.getScript({module:e,script:["/web/fis",e,$ocms.auth.locale||"de","js"].join("."),css:["/web/fis",e,"css"].join("."),condition:"function"!=typeof $ocms[e].init2},(function(){$ocms[e].init2()}))},error:function(){$("#contentframe").empty()}})))};var $fis={auth:{},db:function(){$("#mainmenu_activemodule").text($t.ov);let t=$(this).empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$ocms.wdg.call(i,{wdg:n})}))},loading:e})},ValidateEmail:function(t){return!!/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(t)},cf:t=>{let e=$("#contentframe");return!0===bool(t,!1)&&e.empty().rC("hd"),e},lf:t=>{let e=$("#listframe");return!0===bool(t,!1)&&e.empty().aC("hd").rC("fix"),e},frm_edit:function(t){let e=$fis.cf(!1),n=e.children(".cfrm"),i=e.children(".edit_frm");return n.length<1?n=$$.dc("cfrm hd").prependTo(e):!0===bool(t,!1)&&n.empty(),i.length<1&&(i=$$.dc("edit_frm").insertAfter(n)),i.empty()},frm_list:function(t,e){let n=$fis.cf(!1),i=n.children(".cfrm"),o=n.children(".list_frm");return i.length<1?i=$$.dc("cfrm hd").prependTo(n):!0===bool(t,!1)&&i.empty(),!0===bool(e,!1)&&n.children(".edit_frm").remove(),o.length<1&&(o=$$.dc("list_frm").appendTo(n)),o.empty()},lfm:()=>{let t=$fis.lf(!1),e=t.children(".lfrm");return e.length<1&&(e=$$.dc("lfrm").prependTo(t)),e},getAuth:(t,e)=>new Promise(((n,i)=>{$fis.auth[t]&&!1===bool(e,!1)?n($fis.auth[t]||-1):$ocms.postXT({url:$ocms.url("auth"),data:{module:t},success:e=>{$fis.auth[t]=e.auth||-1,n($fis.auth[t]||-1)},error:()=>{i()}})})),prepAuth:t=>new Promise(((e,n)=>{$ocms.postXT({url:$ocms.url("auth"),data:{module:t,array:1},success:t=>{$.extend($fis.auth,t||{})},complete:()=>{e()}})})),isAuth:(t,e)=>($fis.auth[t]||-1)>=(e||1),resetPass:function(t,e){confirm($t.smsc)&&($ocms.postXT({url:$ocms.url("account/sms"),data:{fn:"pwc"}}),$ocms.dlgform($fd.rsp.clone(),{title:$t.rsp||"",submit:function(t){var e=$(this).ldng(1),n=$.extend({loginaccount:$ocms.auth.account||""},e.serializeObject(!0,{typedvalues:!0}));(n.npw||"")!==(n.npwc||"")?e.find('input[name="npw"]:first')[0].setCustomValidity($t.pnm):$ocms.postXT({url:$ocms.url("account/changepassword"),data:n,success:function(t){alert($t.cps),e.trigger("modal_close")},error:function(t){alert($t.rspf[t.getResponseHeader("x-ocms-std")])},complete:function(){e.ldng(0)},timeout:6e4})}}))},wdg:function(t){let e=$(this).empty();$ocms.postXT({url:$ocms.url("wdg/one"),data:{short_name:t.wdg},timeout:9e4,success:function(n,i,o){let r=t.wdg,s=n[r];if(!s)return void e.ldng(0);let a=$.inArrayRegEx("dblwidth",s.rendering_options)>-1,c=$.inArrayRegEx("tiny",s.rendering_options)>-1;e.toggleClass("dbl",a&&!c).toggleClass("tny",c);$$.dc("wdg_hd",e,{title:ne(s.description,$t.wdc)}).toggleClass("dbl",a).text(ne(s.name,t.wdg)).dblclick((function(t){t.stopPropagation(),$fis.wdg.call(e,{wdg:r})}));let l=$$.dc("wdg_cnt",e).toggleClass("dbl",a).hide(),u=$.inArrayRegEx("bgcolor",s.rendering_options);switch(u>-1&&l.css("backgroundColor",s.rendering_options[u].toString().right(":")),s.type){case"table":var d=$$.tblset({},l),h=$$.tr().appendTo(d.hd),p=$t.wdg[r.indexOf("wdg_ev_")>=0?"wdg_ev_":r]||{};$.each(s.columns,(function(t,e){var n=p[e]?p[e].label:e;$$.th().text(n).appendTo(h)})),$.each(s.data,(function(t,e){var n=$$.tr().appendTo(d.bdy);$.each(s.columns,(function(t,i){var o=$$.td().appendTo(n);e[i]instanceof Date||!0===$ocms.isJSONDateString(e[i])?o.text(fdt(e[i],$t.dateformat)):o.rwText(e[i])}))})),$.inArray("firstrow_bold",s.rendering_options)>-1&&h.nextAll("tr:first").css("font-weight","bold");break;case"ind":$$.dc("ind",l).addClass("sts_"+(s.data.status||"")).append([$$.dc("ind").text(s.data.value),$$.lbl(s.data.label)]);break;case"image_url":l.css("background","url('"+s.url+"') no-repeat center center transparent");break;case"image_base64":l.css("background","url('data:image/png;base64,"+s.image+"') no-repeat center center transparent");break;case"html":if(l.html(s.html),$.inArray("reload_10min",s.rendering_options)>-1){var f=l.find("iframe");setTimeout((function(){f.attr("src",(function(t,e){return e}))}),6e5)}}$.inArray("reload_30min",s.rendering_options)>-1&&"html"!==s.type&&setTimeout((function(){$fis.wdg.call(e,{wdg:r})}),18e5),l.slideDown(150)},error:function(t){e.slideUp(150),$fis.failure.call(this,t)},complete:function(){e.ldng(0)}})},ov:function(){$fis.lf(!0);let t=$("#contentframe").empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$fis.wdg.call(i,{wdg:n})}))},loading:e})}};$fis.notifications={connection:null,init:function(){"undefined"!=typeof signalR&&null===this.connection&&$ocms.auth.useraccount_id&&(this.ensureFrame(),this.connection=(new signalR.HubConnectionBuilder).withUrl("/notifications").withAutomaticReconnect().build(),this.connection.on("notification",(t=>{this.push(t)})),this.connection.onclose((()=>{console.warn("Notification connection closed; retrying in 5s."),this.connection=null,setTimeout((()=>this.init()),5e3)})),this.start())},start:function(){this.connection.start().catch((t=>{console.warn("Notification connection failed to start; retrying in 5s.",t),this.connection=null,setTimeout((()=>this.init()),5e3)}))},ensureFrame:function(){$("#notification_frame").length<1&&$("
      ",{id:"notification_frame"}).appendTo($("footer:first").length?"footer:first":"body")},push:function(t){this.ensureFrame(),t=t||{};let e=$("
      ",{class:"notification_item"}).addClass((t.severity||"info").toLowerCase()).append($("
      '),n=e.find(".form-body"),i=null;e.find("form").submit((function(t){t.preventDefault();var o=$(this).serializeObject(!0),r=null===i,s=r?"spwc":"spw";return $ocms.postXT.call(this,{url:$ocms.url(s),data:o,complete:function(){r?(n.append('
      Ihnen wurde ein Code per SMS zugesandt.
      Bitte tragen Sie den hier ein:
      '),i=$('
      ').appendTo(n)):(alert($t.l13),e.remove())},error:()=>{}}),!1})),e.find(".modal-close").click((function(){e.remove()}));var o=[];$.each($t.l7a.split("\n"),((t,e)=>{Array.prototype.push.apply(o,[$("
      "),$("").text(e)])})),e.find(".modal-note").append($('').text($t.alert)).append(o),e.appendTo("body"),setTimeout((function(){$(".modal").find('input[name="lastname"]').focus()}),600)}};var $$={s:function(t){return $("").text(t)},br:function(){return $("
      ")},sc:function(t,e){return $("").addClass(t).text(e)},td:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},th:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t?n.attr(t):"string"==typeof t&&n.text(t),"object"==typeof e?n.attr(e):"string"==typeof e&&n.text(e),n},tdc:function(t,e,n){return $$.td(e,n).addClass(t)},td2:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},td3:function(t){var e=$('');return"string"===$.type(t)?e.text(t):t instanceof jQuery?e.append(t):"function"==typeof t?t.call(e):e.html(" "),e},tdtr:function(t,e){var n=$$.tr().appendTo(e);return t instanceof jQuery==!0||"string"==typeof t?t.appendTo($$.td().appendTo(n)):!0===Array.isArray(t)&&$.each(t,(function(t,e){$(e).appendTo($$.td().appendTo(n))})),n},tr:function(t,e){var n=$("");return t instanceof jQuery==!0?n.appendTo(t):"object"==typeof t&&n.attr(t),"object"==typeof e&&n.attr(e),n},trc:function(t,e){var n=$("").addClass(t);return e instanceof jQuery==!0?n.appendTo(e):"object"==typeof e&&n.attr(e),n},d:function(t){return $("
      ").attr(t||{})},dc:function(t,e,n,i){var o=$("
      ").addClass(t);return e instanceof jQuery==!0?o.appendTo(e):"object"==typeof e?o.attr(e):"function"==typeof e?o.click(e):"string"==typeof e&&o.text(e),"string"==typeof n?o.text(n):"object"==typeof n?o.attr(n):"function"==typeof n&&o.click(n),"string"==typeof i?o.text(i):"object"==typeof i?o.attr(i):"function"==typeof i&&o.click(i),o},df:function(t){return $("
       
      ").attr(t||{})},opt:function(t,e,n){var i=$("");return"string"==typeof t?i.attr("value",t):"object"==typeof t&&i.attr(t),"string"==typeof e?i.text(e):"object"==typeof e&&i.attr(e),"object"==typeof n&&i.attr(n),i},eOpt:function(t){var e=$('');return t&&e.attr("selected","selected"),e},tbl:function(t){return $("
      ").attr(t||{})},tblc:function(t){return $("
      ").addClass(t)},thead:function(t){let e=$("");return t instanceof jQuery&&e.prependTo(t),e},tbody:function(t){let e=$("");return t instanceof jQuery&&e.appendTo(t),e},tblset:function(t,e){let n=$$.tbl(t||{});return e instanceof jQuery&&e.append(n),{tbl:n,hd:$$.thead().appendTo(n),bdy:$$.tbody().appendTo(n)}},i:function(t){return $("").attr(t||{})},img:function(t,e){return $("").attr("src",t).attr(e||{})},sel:function(t){return $("").attr(t||{})},btn:function(t){return $("").attr(t||{})},a:function(t){return $("").attr(t||{})},li:function(t){return $("
    • ").attr(t||{})},ul:function(t){return $("
        ").attr(t||{})},nav:function(t){return $("").attr(t||{})},lbl:function(t,e){var n=$("");return"string"==typeof t&&n.text(t),"object"==typeof t?n.attr(t):"object"==typeof e&&n.attr(e),n},txt:function(t){return $("").attr(t||{})},0:function(t,e){return $("<"+t+">").attr(e||{})},bbtn:function(t,e){return $$.btn({type:"button",class:"btn"}).addClass(e).text(t)},svg:t=>$(document.createElementNS("http://www.w3.org/2000/svg",t))};function getMonday(t){var e=(t=new Date(t)).getDay(),n=t.getDate()-e+(0==e?-6:1);return new Date(t.setDate(n))}function $lf(t){var e=void 0===t?null:"number"==typeof t&&1!==t||"boolean"==typeof cl&&!1===t;return $("#listframe").tC("hd",e).is(".hd")}function $nuf(t){if(t&&t.stopPropagation(),!$(this).is(".disabled")){var e=function(t){t.removeClass("vis").find("li.dropdown").removeClass("open").removeClass("vis").attr("aria-expanded","false")},n=$(this).parent("li.dropdown");if(n.length>0){n.tC("open"),navs=!0===n.is(".open")?"true":"false",n.attr("aria-expanded",navs);var i=n.closest("nav");i.find("li.dropdown").not(n.parentsUntil("nav")).not(n).removeClass("open").attr("aria-expanded","false"),!1===n.is(".open")&&n.find("li.dropdown").removeClass("open").attr("aria-expanded","false"),e($("nav").not(i))}else e($("nav"))}}function $tbr(){return $lf(0),$("#topbar").ocmsmenu([])}function $lfr(){return $("#sidebar").empty(),$("#listframe").removeClass("fix").addClass("hd").empty()}function $cfr(){return $tbr(),$("#contentframe").empty()}function jObj(t,e){let n={};if("{"===(t||"").substr(0,1))try{n=JSON.parse(t)}catch(t){n={}}return n[e]||""}function string(t,e){var n,i=t||"";return $.each(e||[],(function(t,e){n=new RegExp("\\{"+t.toString()+"\\}","ig"),i=i.replace(n,e)})),i}function init_tooltip(t){var e=!0===("boolean"==typeof t&&t)&&"mouse";$("[title]").qtip({position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1}),$("div.tooltiptext").each((function(){$(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:$(this).clone()},position:{target:e,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden}})}))}String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")},String.prototype.left=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.slice(0,e):""}return this.substring(0,t)},String.prototype.right=function(t){if("string"===$.type(t)){var e=this.indexOf(t);return e>0?this.substring(this.length-e):""}return this.substring(this.length-t)},Array.prototype.move=function(t,e){if(e>=this.length)for(var n=e-this.length;1+n--;)this.push(void 0);return this.splice(e,0,this.splice(t,1)[0]),this},function(t){t.fn.appendToIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.appendTo(e),i},t.fn.appendIf=function(e,n){var i=t(this),o="function"==typeof n?n(i):n;return!0===("boolean"!=typeof o||o)&&i.append(e),i},t.fn.rwText=function(e,n,i){var o=t(this).empty();i=t.extend({wrap:!0},i);var r=!0===Array.isArray(e)?e:(null==e?"":String(e)).split("\n");return t.each(r,(function(t,e){""!==(e||"")&&(t>0&&o.append($$.br()),o.append(!0===i.wrap?$$.s(e):e))})),n&&o.attr("title",n),o},t.fn.loadSel=function(e,n,i){if("SELECT"===t(this).prop("tagName").toUpperCase()){var o=t(this);$ocms.postXT.call(this,{url:e,data:n||{},success:function(e){t.each(e,(function(){o.append($$.opt(e.value,e.text))}))},complete:function(){o.ldng(0),"function"==typeof i&&i.call(o)}})}},t.fn.emptyWithEditors=function(e){var n=t(this);return n.find(":input.tinymce").each((function(e,n){try{var i=tinymce.get(t(n).attr("id"));i&&i.remove()}catch(e){t.noop()}})),n.empty()},t.fn.cssValue=function(t){if(this.length>0){var e=this.css(t)||"";if(""===e)return 0;var n=/(^[\d\.]*)(\D{1,3}$)/gi.exec(e);return null!==n?"rem"===n[2]?$ocms.rpx(parseFloat(n[1])):parseFloat(n[1]):!1===isNaN(e)?parseFloat(e):0}return 0},t.fn.veryInnerHeight=function(){let e=e=>t(this).cssValue(e);return t(this).innerHeight()-e("padding-top")-e("padding-bottom")},t.fn.veryInnerWidth=function(){let e=e=>t(this).cssValue(e);return t(this).innerWidth()-e("padding-left")-e("padding-right")},t.fn.marginWidth=function(){let e=e=>t(this).cssValue(e);return e("margin-left")+e("margin-right")},t.fn.marginHeight=function(){let e=e=>t(this).cssValue(e);return e("margin-top")+e("margin-bottom")},t.inArrayRegEx=function(e,n,i){var o="regexp"===t.type(e)?e:new RegExp(e);if(!n)return-1;for(var r=i=i||0;r7){i=e.split(","),o=(n||(t<0?"rgb(0,0,0)":"rgb(255,255,255)")).split(",");var c=a(i[0].slice(4)),l=a(i[1]),u=a(i[2]);return"rgb("+(s((a(o[0].slice(4))-c)*r)+c)+","+(s((a(o[1])-l)*r)+l)+","+(s((a(o[2])-u)*r)+u)+")"}var d=(i=a(e.slice(1),16))>>16,h=i>>8&255,p=255&i;return"#"+(16777216+65536*(s((((o=a((n||(t<0?"#000000":"#FFFFFF")).slice(1),16))>>16)-d)*r)+d)+256*(s(((o>>8&255)-h)*r)+h)+(s(((255&o)-p)*r)+p)).toString(16).slice(1)},t.fn.IN=function(e){return t(this).fadeIn(400,e),t(this)},t.fn.OUT=function(e){return t(this).fadeOut(400,e),t(this)},t.fn.tooltip=function(e,n){var i=!0===("boolean"==typeof e&&e)&&"mouse",o="boolean"==typeof n&&n,r=t(this);return r.each((function(){var e=o?t(this).find(".tooltiptext"):t(this).children(".tooltiptext");t(e).length>0?e.each((function(){var e=t(this);t(this).filter(":not(:empty)").parent().qtip({suppress:!1,content:{text:e.clone()},position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},show:{effect:!1},hide:{effect:!1}}),e.remove()})):t(this).qtip({position:{target:i,adjust:{x:2,y:2},viewport:!0},events:{hidden:$ocms.tooltip_hidden},effect:!1})})),r},t.fn.rC=function(e){return t(this).removeClass(e)},t.fn.aC=function(e){return t(this).addClass(e)},t.fn.tC=function(e,n){return t(this).toggleClass(e,n)}}(jQuery),function(t){t.fn.ocmsmenu=function(e,n){var i=t(this);return $ocms.menu.call(i,e,n),i},t.fn.activatemenu=function(){var e=t(this).filter("nav");return e.find("a").not(".on").addClass("on").click($nuf),e.find(".nav-btn").not(".on").addClass("on").click((function(e){e.stopPropagation();var n=t(this);t(n.attr("data-target")).tC(n.attr("data-toggle"))})),e}}(jQuery);class ObjectArray extends Array{isEmpty(){return 0===this[0].length}static get[Symbol.species](){return Array}filter(t){return"function"==typeof t?new ObjectArray(this[0].filter(t)):this}remove(t){if("function"!=typeof t)return this;{let e=this[0].findIndex(t);for(;e>-1;)this[0].splice(e),e=this[0].findIndex(t)}}sortBy(t){return"function"==typeof t&&this[0].sort(t),this}sortString(t){return this[0].sort(((e,n)=>{let i=(e[t]||"").toString().toUpperCase(),o=(n[t]||"").toString().toUpperCase();return console.debug(i.localeCompare(o)),i.localeCompare(o)})),this}sortNum(t){return this[0].sort(((e,n)=>{let i=e[t],o=n[t];return!0===isNaN(o)&&!1===isNaN(i)||io?1:0})),this}sum(t){return this[0].reduce(((e,n)=>e+(!0===isNaN(n[t])?0:n[t])),0)}groupBy(t){return this[0].reduce((function(e,n){let i=n[t];return e[i]||(e[i]=[]),e[i].push(n),e}),{})}each(t){if("function"==typeof t){let e=!1;this[0].forEach(((n,i,o)=>{if(!1===e){let r=t(n,i,o);"boolean"==typeof r&&!1===r&&(e=!0)}}))}}get toArray(){return this[0]}}class NumArray extends Array{sum(){return this.reduce(((t,e)=>t+e))}first(){return this[0]}last(){return this[this.length-1]}average(){return this.sum()/this.length}range(){let t=this.map((t=>t)).sort();return{min:t[0],max:t[this.length-1]}}static get[Symbol.species](){return Array}}$ocms.ocmsmenu=[{lbl:"",id:"m_home",ico:"glyphicon glyphicon-home",fnc:"init:home"},{fnc:"separator"}],function(t){t.multline=function(t){let e=t.split("\n"),n=$$.d();return $.each(e,((t,e)=>{n.append($$.s(e))})),n.html()},t.tooltip_hidden=function(t,e){$(this).remove(),e.rendered=!1},t.isJSONDateString=function(t){return"string"==typeof t&&/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?)?$/.test(t)},t.failure=function(e){11110===(e.internalCode||-1)?t.login.dlg():alert($t.f1+"\n"+(e.internalText||""))},t.getScript=function(e,n){var i=[],o=[],r=function(t){return"string"==typeof t&&""!==(t||"")},s=function(t,e){!0===bool(e.condition,!0)&&(""!==(e.script||"")&&o.push({url:e.script,module:e.module||""}),!0===r(e.css||"")?i.push(e.css):!0===Array.isArray(e.css)&&Array.prototype.push.apply(i,e.css.filter(r)))};!0===r(e||"")?o.push(e):!0===Array.isArray(e)?$.each(e,s):"object"==typeof e&&""!==(e.script||"")&&s(0,e);let a=[];$.each(i,(function(t,e){""!==(e||"")&&a.push(loadCSS(e))}));let c=o.map((function(e,n){let i=e.url,r=e.module||"";if(""===r){let t=new Promise((function(t,e){try{!async function(){$.ajax({url:i,dataType:"script",success:function(){t(o)},error:function(){e(o)},timeout:3e4})}()}catch(t){console.debug(t.message+"%o",t)}}));return t}return t.loadmodule(r,i,e.alias)}));Promise.all(c).then(n)},t.loadmodule=function(e,n,i){let o=new Promise((function(o,r){!async function(){try{let s=(n.startsWith("/")||n.startsWith(".")?"":"/")+n;import(s).then((n=>{t[e]=n[i||"default"],o(e)})).catch((t=>{console.debug(t.message+"%o",t),r(e)}))}catch(t){console.debug(t.message+"%o",t)}}()}));return o},t.ocms_auth=function(e,n,i,o){!1===$.isPlainObject(t.auth.modules)&&(t.auth.modules={});var r=0;t.auth.modules[e+(i||"")]?((r=t.auth.modules[e+(i||"")])<2&&(i||"")===auth.guid&&(r=2),r>=(n||0)&&o(false)):t.postXT({url:t.url("auth"),data:{module:e,person_guid:i||""},success:function(s){r=s[e],t.auth.modules[e+(i||"")]=r,r<2&&(i||"")===t.auth.person_guid&&(r=2),r>=(n||0)&&o(false)},error:function(e){t.failure.call(this,e)}})},t.auth.locale="de",t.ocms_prepauth=function(e,n,i){t.postXT({url:t.url("auth"),data:{fn:"csv",modules:e,person_guid:n||""},success:function(e){t.ocms_regauth(e)},error:function(e){t.failure.call(this,e)},complete:function(){i()}})},t.ocms_regauth=function(t){$.each(t||{},(function(t,e){auth.modules[t]=parseInt(e)}))},t.init=function(e){var n="string"==typeof e?e:(e.data||{}).fn||"";""!==n&&("home"===n?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),t.ov.call($("#contentframe"))):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),t.postXT({url:t.url(n+"/auth"),success:function(e){void 0===t[n]&&(t[n]={}),t[n].auth=e,e.manage>0&&t.getScript({module:n,script:["web/imdl",n,t.auth.locale||"de","js"].join("."),css:["web/imdl",n,"css"].join("."),condition:"function"!=typeof t[n].init2},(function(){t[n].init2()}))},error:function(){$("#contentframe").empty()}})))},t.menuarray=function(t){this.array=[],this.sep=function(){this.length>0&&"separator"!==this.array[array.length-1].fnc&&this.push({fnc:"separator"})},this.push=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.push.apply(this.array,t):"object"==typeof t&&this.array.push(t),t)},this.unshift=function(t){return void 0===t?null:(!0===Array.isArray(t)?Array.prototype.unshift.apply(this.array,t):"object"==typeof t&&this.array.unshift(t),t)},this.push(t)},t.menu=function(e,n){e=e||[];var i=$(this).removeClass("vis");if(!0===bool(n,!0)&&!1===i.is("#mainmenu")&&i.empty(),!1===bool(n,!1)&&i.is("#sidebar,#topbar")&&(e.unshift({id:"sbctrl",glyph:"glyphicon-th-list",aclass:"fbtn",fnc:function(){$lf()}}),$lf(0)),0===(e||[]).length)i.empty().addClass("hd");else{i.removeClass("hd");var o=!0===i.is("nav")?i:i.children("nav");1!==o.length&&(o=$("").tC("nv",i.is("#sidebar")).tC("ctxt",i.is("#topbar")).appendTo(i));var r,s=$$.ul().appendTo(o),a=function(t,e){var n=$(this).addClass("dropdown submenu");t.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"}),""!==(e.ico||"")&&t.prepend($$.sc("ico "+e.ico));var i=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(n);$.each(e.itm||[],(function(t,e){r.call(i,e)}))},c=function(t){$(this).tC("disabled","boolean"==typeof t.disabled?t.disabled:"string"==typeof t.disabled&&"subs"===t.disabled&&0===(t.itm||[]).length)};r=function(e){var n,i=$$.li({id:e.id}).attr(e.attr||{}).addClass(e.lclass).appendTo($(this)),o="string"==typeof e.fnc&&""!==e.fnc?e.fnc.split(":")[0]:"";""!==o&&"init"!==o?i.attr("role",o).appendIf($$.s(e.lbl),""!==ne(e.lbl)):(n=$$.a({class:"on",role:"button"}).addClass(e.aclass).appendTo(i).append($$.s(e.lbl)),c.call(n,e),(e.itm||[]).length>0&&a.call(i,n,e),n.click($nuf),"function"==typeof e.fnc?n.click(e.data||{},e.fnc):"init"===o&&n.click($.extend({},e.data||{},{fn:e.fnc.split(":")[1]}),t.init))},$.each(e,(function(e,n){var i,o=$$.li({id:n.id}).attr(n.attr||{}).addClass(n.lclass),a="string"==typeof n.fnc&&""!==n.fnc?n.fnc.split(":")[0]:"";if(""!==a&&"init"!==a)o.attr("role",a).appendIf($$.s(n.lbl),""!==ne(n.lbl));else{if(i=$$.a({class:"on",role:"button"}).addClass(n.aclass).appendTo(o),c.call(i,n),""!==(n.lbl||"")&&i.append($$.s(n.lbl)),""!==(n.ico||"")&&i.prepend($$.sc("ico "+n.ico)),""!==(n.glyph||"")&&i.prepend($$.sc("glyphicon "+n.glyph)),(n.itm||[]).length>0){o.addClass("dropdown"),i.append($$.sc("caret dd")).addClass("dds dropdown-toggle").attr({"aria-expanded":"false"});var l=$$.ul({class:"dropdown-menu",role:"menu"}).appendTo(o);$.each(n.itm||[],(function(t,e){r.call(l,e)}))}(n.sel||[]).length>0||(i.click($nuf),"function"==typeof n.fnc?i.click(n.data||{},n.fnc):"init"===a&&i.click($.extend({},n.data||{},{fn:n.fnc.split(":")[1]}),t.init))}o.appendTo(s)})),o.activatemenu()}},t.easytbl=(t,e)=>{e=e||{};let n=$$.tbl().addClass(e.class).css("border-collapse","collapse"),i=($$.tbody(n),!0===bool(e.frame,!1)?{padding:"5px",border:"1px solid #727272"}:{});if(!0===Array.isArray(e.header)){let t=$$.thead(n);$.each(e.header,((n,o)=>$$.th(t).css(e.cellcss||i).rwText(o)))}else if(!0===bool(e.header,!1)&&(t||[]).length>0){let o=$$.thead(n);$.each(Object.keys(t[0]),((t,n)=>$$.th(o).css(e.cellcss||i).rwText(n)))}return $.each(t||[],((t,o)=>{let r=$$.tr();$.each(o,((t,n)=>{n=n||"";let o=$$.td(r).css(e.cellcss||i);n instanceof jQuery?o.append(n):"string"==typeof n&&("<"===n.substring(0,1)?o.append(n):o.text(n))})),n.append(r)})),n},t.dlgtbl=(e,n,i)=>{i=i||{};let o=t.easytbl(e,i);t.dlg(o,$.extend({title:n},i))},t.dlg=function(t,n){n=n||{};let i=$("body > .modal").length>0,o=t=>typeof n[t],r=t=>"function"===o(t);if(!0===bool(n.exclusive,!0)&&!0===i)return void alert($t.dbldlg||"Es ist bereits ein Dialog geöffnet");let s=$$.dc("modal",$("body")),a=$$.dc("modal-dialog",s);!1===isNaN(n.zindex)?s.css("zIndex",n.zindex):!0===i&&s.css("zIndex",parseInt($("body > .modal:last").cssValue("zIndex"))+200),!1===isNaN(n.zindex_min)&&s.cssValue("zIndex")').appendTo(d)),""!==ne(n.title)&&(c=$$.dc("modal-header",d),$("

        ").text(n.title).appendTo(c));let p=$$.dc("modal-body",d),f=$$.dc("modal-footer",d);t instanceof jQuery==!0&&p.append(t);let m=function(t){t&&"function"==typeof t.stopPropagation&&t.stopPropagation(),a.removeClass("in"),!0===r("closing")&&n.closing.call(d),p.hide().emptyWithEditors(),s.remove(),!0===r("close")&&n.close.call(d)};if(d.find(":input[required]").length>0&&($$.dc("note_required",f).append($$.sc("ind_required","*")).append($$.s($t.t1||"Eingabe erforderlich")),$$.dc("note_invalid",f).append($$.s($t.t2||"Bitte überprüfen Sie Ihre Eingaben im Formular."))),!0===r("cancel")){$$.bbtn(n.cancelbutton||"Abbrechen","cancel").attr({type:"button",role:"cancel"}).appendTo(f).click((function(t){n.cancel.call(d,t);t.stopPropagation(),m()}))}if(!0===r("confirm")){let t=$$.bbtn(n.button||"OK","confirm").attr({type:!0===bool(n.form,!1)?"submit":"button",role:"confirm"}).appendTo(f);!0===h?(d.submit((function(t){try{n.confirm.call(d,t)}finally{t.preventDefault()}return!1})),d.on("modal_submit",(function(){n.confirm.call(d,e)}))):(t.click((function(t){n.confirm.call(d,t);t.stopPropagation()})),d.on("modal_submit",(function(){t.click()})))}else!0===h&&d.submit((function(t){return t.preventDefault(),!1}));return d.on("modal_close",(function(){m()})),l.click(m),!0===r("opening")&&n.opening.call(d),a.addClass("in"),ne(n.mode).indexOf("maxbody")>-1&&p.css("min-height",(u.height()-c.outerHeight()-f.outerHeight()).toString()+"px"),!0===r("open")&&n.open.call(d),{hd:c,bdy:p,ft:f,ct:u,dlg:a,c:d}},t.mform=function(e){let n=$$.dc("form-body"),i=Array.isArray(e)?e:e instanceof fields_definition?e.fields:[];return $.each(i||[],(function(e,i){let o=i.type||"";if("ignore"===o)return!0;let r=$$.dc("form-group",n),s=i.id||"dlg_"+(i.name||"")+("html"===i.type?"_"+(65536*(1+Math.random())||0).toString(16).substr(9):""),a=$$.lbl(i.label||i.name,{for:s}).appendTo($$.dc("form-itm",r)),c=$$.dc("form-itm",r),l=$$.i({id:s,name:i.name,placeholder:i.placeholder,type:i.type});switch(o){case"email":i.pattern=ne(i.pattern,"[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$");break;case"url":i.pattern=ne(i.pattern,"https?://.+");break;case"number":i.pattern=ne(i.pattern,"[-+]?[0-9]*[.,]?[0-9]*"),l.attr("type","text"),l.attr("inputmode","decimal"),l.attr("step",i.precision||"any"),l.attr("data-format","float");break;case"integer":case"int":i.pattern=ne(i.pattern,"[-+]?[0-9]*"),l.attr("type","number"),l.attr("data-format","integer");break;case"date":if(""!==ne(i.pattern,$t.datepattern)&&(i.pattern=ne(i.pattern,"("+$t.datepattern+")|([0-9]{4}.(0[1-9]|1[012]).(0[1-9]|1[0-9]|2[0-9]|3[01]))")),""!==ne(i.placeholder,$t.dateplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.dateplaceholder)),"string"==typeof i.value){var u=i.value.substr(0,10);i.value="date"!==l.prop("type")?fdt(u+"T00:00:00",ne(i.dateformat,$t.dateformat)):u}l.attr("data-format","date:"+ne(i.dateformat,$t.dateformat)+";yyyy-MM-dd");break;case"datetime":l.attr("type","datetime-local"),""!==ne(i.pattern,$t.datetimepattern)&&(i.pattern=ne(i.pattern,"("+$t.datetimepattern+")|([0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])\\s([0-5][0-9]):([0-5][0-9]))")),""!==ne(i.placeholder,$t.datetimeplaceholder)&&l.attr("placeholder",ne(i.placeholder,$t.datetimeplaceholder)),"string"==typeof i.value&&"T"===i.value.substr(10,1)&&(i.value="datetime"!==l.prop("type").substr(0,8)?fdt(i.value,ne(i.datetimeformat,$t.datetimeformat)):i.value),l.attr("data-format","datetime:"+ne(i.datetimeformat,$t.datetimeformat)+";yyyy-MM-dd HH:mm:ss");break;case"hidden":r.addClass("hd");break;case"html":case"text":l=$$.txt({id:s,name:i.name,placeholder:i.placeholder,type:i.type}),l.tC("tinymce","html"===i.type);break;case"bool":case"boolean":i.url=[{value:"true",label:($t||{}).true||"Yes"},{value:"false",label:($t||{}).false||"No"}],"boolean"==typeof i.value&&(i.value=i.value?"true":"false");case"select":l=$$.sel({id:s,name:i.name,type:i.type}),!1===bool(i.required,!1)&&$$.eOpt().appendTo(l);try{var d=function(t){!0===Array.isArray(t)&&$.each(t,(function(t,e){"string"==typeof e?$$.opt(e,e).appendTo(l):!0===Array.isArray(e)?$$.opt(e[0],e[1]).appendTo(l):"object"==typeof e&&$$.opt(e.value,e.label||e.text).appendTo(l)}))};!0===Array.isArray(i.url)?d(i.url):"function"==typeof i.url?i.url.call(l):"string"==typeof i.url&&t.postXT({url:i.url,success:d})}catch(t){$.noop()}break;default:""!==ne(i["max-length"])&&l.attr("max-length",i["max-length"])}""!==ne(i.pattern)&&l.attr("pattern",i.pattern),l.val(i.value).change(),l.change((function(){$(this)[0].setCustomValidity("")})),l.addClass("form-control").prop("required",bool(i.required,!1)).prop("readonly",bool(i.readonly,!1)).appendTo(c),!0===bool(i.required,!1)&&a.append($$.sc("ind_required","*")),"object"==typeof i.attr&&l.attr(i.attr),"object"==typeof i.prop&&l.prop(i.prop),"string"==typeof i.class&&l.addClass(i.class),"function"==typeof i.change&&(l.change(i.change),!0===bool(i.applychange,!1)&&void 0!==i.value&&l.change()),""!==(i.note||"")&&$$.dc("form-note",c).rwText(i.note),"function"==typeof i.complete&&i.complete.call(l)})),n},t.initMCE=function(t,e){t=$(t),e=e||{};try{let n={target:t[0],inline:!1,width:e.width||"100%",statusbar:!1,document_base_url:window.location.origin+"/",content_style:"ph:before {content: '«'; color: #BBB; font-style:italic; } ph:after {content: '»'; color: #BBB; font-style:italic; } ph { color: #AAA; font-style:italic; }",relative_urls:!1,remove_script_host:!1};!0===bool(e.hidemenu,!1)&&(n.menubar=!1,n.menu={}),!0===bool(e.hidetoolbar,!1)&&(n.toolbar=!1),$.extend(n,e||{}),tinymce.init(n)}catch(t){alert(t.message)}},t.dlgform=function(e,n){n=n||{};let i,o=$$.dc("frm").append(t.mform(e||[]).addClass("stacked"));n.addcontent instanceof jQuery&&o.append(n.addcontent),"function"==typeof n.submit?i=n.submit:"function"==typeof n.success&&(i=function(e){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject(bool(n.checkvalidity,!0),{typedvalues:bool(n.typedvalues,!1)}));""!==(n.url||"")?t.postXT({url:n.url,data:o,success:function(t){n.success.call(this,t),i.trigger("modal_close")},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4}):(n.success.call(this,o),i.trigger("modal_close"))});let r={form:!0,title:n.title||"",button:n.button||$t.submit,confirm:i,size:n.size||[500,600],open:function(){let e=$(this).find(".tinymce");e.length>0&&t.initMCE(e,n.tinymce||{})}};return t.dlg.call(this,o,r)},t.login.dlg=function(e){e=e||{};let n=[{name:"userinfo",label:$t.l1,type:"string",value:t.auth.login,change:t.login.uichange,required:!0},{name:"userlogin",type:"hidden",required:!0,value:t.auth.login},{name:"username",type:"string",label:$t.l4,required:!0,readonly:!0,placeholder:$t.l5,value:t.auth.fullname_rev},{name:"userpass",type:"password",label:$t.l3,required:!0,placeholder:$t.l3}];""===(t.auth.account||"")&&n.unshift({id:"dlg_loginaccount",name:"loginaccount",type:"string",required:!0,value:t.auth.account});let i=$$.dc("frm").append(t.mform(n).addClass("stacked")),o=t.dlg.call(this,i,{form:!0,title:$t.l0,button:$t.submit,confirm:function(n){var i=$(this).ldng(1),o=$.extend({loginaccount:t.auth.account||""},i.serializeObject());t.postXT({url:"/vt/login",data:o,success:function(n){""!==((n||{}).login||"")&&(i.trigger("modal_close"),t.auth=n,"object"==typeof e.ajo&&(e.ajo.islogin,$.ajax(e.ajo)))},error:function(){alert($t.l17)},complete:function(){i.ldng(0)},timeout:6e4})},size:[500,600]}),r=$$.dc("modal-content").css("height","auto").attr("novalidate","true").append($$.dc("modal-header").appendIf($("

        ").text(t.auth.accountname),""!==(t.auth.accountname||"")).append($("

        Vereinsmanager

        ")));o.dlg.prepend(r)},t.addNoEntryInfo=function(t){$(this).append($$.dc("noentryinfo").text(t||$t.t11))}}($ocms),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(t){var e=this;do{if(Element.prototype.matches.call(e,t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}),function(t,e){var n,i;"object"==typeof window&&(window[t]=(n=function(t){var e=window,n=document.body,i=document.documentElement,o=Math.max(0,e.pageXOffset||i.scrollLeft||n.scrollLeft||0)-(i.clientLeft||0),r=Math.max(0,e.pageYOffset||i.scrollTop||n.scrollTop||0)-(i.clientTop||0);return{x:t?Math.max(0,t.pageX||t.clientX||0)-o:0,y:t?Math.max(0,t.pageY||t.clientY||0)-r:0}},(i=function(t,e){t&&t instanceof Element&&(this._container=t,this._options=e||{},this._clickItem=null,this._dragItem=null,this._showDragItem="boolean"!=typeof this._options.dragItem||!1!==this._options.dragItem,this._hovItem=null,this._sortLists=[],this._click={},this._dragging=!1,this._dragHandleClass=this._options.dragHandleClass||"",this._parentident=this._options.parentident||"",this._swapdone="function"==typeof this._options.swapdone?this._options.swapdone:null,this._onend="function"==typeof this._options.onend?this._options.onend:null,this._container.setAttribute("data-is-sortable",1),this._container.classList.add("sortable"),this._container.style.position="static",window.addEventListener("mousedown",this._onPress.bind(this),!0),window.addEventListener("touchstart",this._onPress.bind(this),!0),window.addEventListener("mouseup",this._onRelease.bind(this),!0),window.addEventListener("touchend",this._onRelease.bind(this),!0),window.addEventListener("mousemove",this._onMove.bind(this),!0),window.addEventListener("touchmove",this._onMove.bind(this),!0))}).prototype={constructor:i,toArray:function(t){t=t||"id";for(var e=[],n="",i=0;ii.left&&ei.top&&n0?a.mousedown(c).addClass("dctrl"):s.mousedown(c).addClass("dctrl"),t(this)}}(jQuery),$(document).ready((function(){$("html").click((function(t){$nuf()})),$("#listframe").click((function(t){t.stopPropagation(),$nuf()})),$("#mainmenu").ocmsmenu($ocms.ocmsmenu),$("#mainmenu").activatemenu()})),$.extend($t,{m_inv:"Rechnungen",m_req:"Aufträge",m_rep:"Berichte",m_todo:"ToDos",m_bcd:"BankBuchungen",m_admin:"Administration",m_admin_status:"Systemüberwachung",m_usr:"Benutzerverwaltung",rsp:"Passwort ändern",pnm:"Die Passwörter stimmen nicht überein",cps:"Das neue Passwort wurde gespeichert.",pwr:"Bitte wählen Sie ein starkes Passwort (min 8 Zeichen, davon jeweils min 2 Zahlen, kleine und große Buchstaben, Sonderzeichen sind optional).",smsc:"Sie beötigen für diese Funktion einen SMS-Code.\nSoll dieser nun versandt werden?",wdc:"Doppelt klicken, um die Box zu aktualisieren.",wdg:{}}),$t.rspf={sms:"Der SMS-Code konnte nicht bestätigt werden",valid:"Das alte Passwort ist nicht korrekt",requirements:"Das Passwort entspricht nicht den Anforderungen.\n"+$t.pwr},$fd={rsp:new fields_definition("","",[{name:"opw",label:"aktuelles Passwort",type:"password",required:!0,attr:{"auto-complete":"current-password"}},{name:"npw",label:"neues Passwort",type:"password",required:!0,pattern:"(.{6,})",attr:{"auto-complete":"new-password"}},{name:"npwc",label:"neues Passwort (Bestätigung)",type:"password",required:!0,attr:{"auto-complete":"new-password"},note:$t.pwr},{name:"code",label:"SMS-Code",type:"string",required:!0,attr:{"auto-complete":"one-time-code"}}])},$ocms.init=function(t){var e="string"==typeof t?t:(t.data||{}).fn||"";""!==e&&("home"===e?($cfr(),$lfr(),$("#topbar").ocmsmenu([],!0),$("#activemodule").text($t.ov),$fis.ov()):($cfr(),$lfr(),$("#topbar").ocmsmenu([]),$ocms.postXT({url:$ocms.url(e+"/auth"),success:function(t){void 0===$ocms[e]&&($ocms[e]={}),$ocms[e].auth=t,t.manage>0&&$ocms.getScript({module:e,script:["/web/fis",e,$ocms.auth.locale||"de","js"].join("."),css:["/web/fis",e,"css"].join("."),condition:"function"!=typeof $ocms[e].init2},(function(){$ocms[e].init2()}))},error:function(){$("#contentframe").empty()}})))};var $fis={auth:{},db:function(){$("#mainmenu_activemodule").text($t.ov);let t=$(this).empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$ocms.wdg.call(i,{wdg:n})}))},loading:e})},ValidateEmail:function(t){return!!/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(t)},cf:t=>{let e=$("#contentframe");return!0===bool(t,!1)&&e.empty().rC("hd"),e},lf:t=>{let e=$("#listframe");return!0===bool(t,!1)&&e.empty().aC("hd").rC("fix"),e},frm_edit:function(t){let e=$fis.cf(!1),n=e.children(".cfrm"),i=e.children(".edit_frm");return n.length<1?n=$$.dc("cfrm hd").prependTo(e):!0===bool(t,!1)&&n.empty(),i.length<1&&(i=$$.dc("edit_frm").insertAfter(n)),i.empty()},frm_list:function(t,e){let n=$fis.cf(!1),i=n.children(".cfrm"),o=n.children(".list_frm");return i.length<1?i=$$.dc("cfrm hd").prependTo(n):!0===bool(t,!1)&&i.empty(),!0===bool(e,!1)&&n.children(".edit_frm").remove(),o.length<1&&(o=$$.dc("list_frm").appendTo(n)),o.empty()},lfm:()=>{let t=$fis.lf(!1),e=t.children(".lfrm");return e.length<1&&(e=$$.dc("lfrm").prependTo(t)),e},getAuth:(t,e)=>new Promise(((n,i)=>{$fis.auth[t]&&!1===bool(e,!1)?n($fis.auth[t]||-1):$ocms.postXT({url:$ocms.url("auth"),data:{module:t},success:e=>{$fis.auth[t]=e.auth||-1,n($fis.auth[t]||-1)},error:()=>{i()}})})),prepAuth:t=>new Promise(((e,n)=>{$ocms.postXT({url:$ocms.url("auth"),data:{module:t,array:1},success:t=>{$.extend($fis.auth,t||{})},complete:()=>{e()}})})),isAuth:(t,e)=>($fis.auth[t]||-1)>=(e||1),resetPass:function(t,e){confirm($t.smsc)&&($ocms.postXT({url:$ocms.url("account/sms"),data:{fn:"pwc"}}),$ocms.dlgform($fd.rsp.clone(),{title:$t.rsp||"",submit:function(t){var e=$(this).ldng(1),n=$.extend({loginaccount:$ocms.auth.account||""},e.serializeObject(!0,{typedvalues:!0}));(n.npw||"")!==(n.npwc||"")?e.find('input[name="npw"]:first')[0].setCustomValidity($t.pnm):$ocms.postXT({url:$ocms.url("account/changepassword"),data:n,success:function(t){alert($t.cps),e.trigger("modal_close")},error:function(t){alert($t.rspf[t.getResponseHeader("x-ocms-std")])},complete:function(){e.ldng(0)},timeout:6e4})}}))},wdg:function(t){let e=$(this).empty();$ocms.postXT({url:$ocms.url("wdg/one"),data:{short_name:t.wdg},timeout:9e4,success:function(n,i,o){let r=t.wdg,s=n[r];if(!s)return void e.ldng(0);let a=$.inArrayRegEx("dblwidth",s.rendering_options)>-1,c=$.inArrayRegEx("tiny",s.rendering_options)>-1;e.toggleClass("dbl",a&&!c).toggleClass("tny",c);$$.dc("wdg_hd",e,{title:ne(s.description,$t.wdc)}).toggleClass("dbl",a).text(ne(s.name,t.wdg)).dblclick((function(t){t.stopPropagation(),$fis.wdg.call(e,{wdg:r})}));let l=$$.dc("wdg_cnt",e).toggleClass("dbl",a).hide(),u=$.inArrayRegEx("bgcolor",s.rendering_options);switch(u>-1&&l.css("backgroundColor",s.rendering_options[u].toString().right(":")),s.type){case"table":var d=$$.tblset({},l),h=$$.tr().appendTo(d.hd),p=$t.wdg[r.indexOf("wdg_ev_")>=0?"wdg_ev_":r]||{};$.each(s.columns,(function(t,e){var n=p[e]?p[e].label:e;$$.th().text(n).appendTo(h)})),$.each(s.data,(function(t,e){var n=$$.tr().appendTo(d.bdy);$.each(s.columns,(function(t,i){var o=$$.td().appendTo(n);e[i]instanceof Date||!0===$ocms.isJSONDateString(e[i])?o.text(fdt(e[i],$t.dateformat)):o.rwText(e[i])}))})),$.inArray("firstrow_bold",s.rendering_options)>-1&&h.nextAll("tr:first").css("font-weight","bold");break;case"ind":$$.dc("ind",l).addClass("sts_"+(s.data.status||"")).append([$$.dc("ind").text(s.data.value),$$.lbl(s.data.label)]);break;case"image_url":l.css("background","url('"+s.url+"') no-repeat center center transparent");break;case"image_base64":l.css("background","url('data:image/png;base64,"+s.image+"') no-repeat center center transparent");break;case"html":if(l.html(s.html),$.inArray("reload_10min",s.rendering_options)>-1){var f=l.find("iframe");setTimeout((function(){f.attr("src",(function(t,e){return e}))}),6e5)}}$.inArray("reload_30min",s.rendering_options)>-1&&"html"!==s.type&&setTimeout((function(){$fis.wdg.call(e,{wdg:r})}),18e5),l.slideDown(150)},error:function(t){e.slideUp(150),$fis.failure.call(this,t)},complete:function(){e.ldng(0)}})},ov:function(){$fis.lf(!0);let t=$("#contentframe").empty(),e=$$.d({id:"dashboard_frame"}).appendTo(t);$ocms.postXT({url:$ocms.url("wdg/my"),success:function(t){$.each(t,(function(t,n){var i=$$.dc("wdg_frame",e,{"data-wdg":n}).ldng(1);$fis.wdg.call(i,{wdg:n})}))},loading:e})}};$fis.notifications={connection:null,init:function(){"undefined"!=typeof signalR&&null===this.connection&&$ocms.auth.useraccount_id&&(this.ensureFrame(),this.connection=(new signalR.HubConnectionBuilder).withUrl("/notifications").withAutomaticReconnect().build(),this.connection.on("notification",(t=>{this.push(t)})),this.connection.onclose((()=>{console.warn("Notification connection closed; retrying in 5s."),this.connection=null,setTimeout((()=>this.init()),5e3)})),this.start())},start:function(){this.connection.start().catch((t=>{console.warn("Notification connection failed to start; retrying in 5s.",t),this.connection=null,setTimeout((()=>this.init()),5e3)}))},ensureFrame:function(){$("#notification_frame").length<1&&$("
        ",{id:"notification_frame"}).appendTo($("footer:first").length?"footer:first":"body")},push:function(t){this.ensureFrame(),t=t||{};let e=$("
        ",{class:"notification_item"}).addClass((t.severity||"info").toLowerCase()).append($("