Split Admin module into Status and User Management under one sidemenu dropdown
Playwright Tests / test (push) Canceled after 0s

Splits fn=admin into two independent modules: the original status
dashboard stays at fn=admin, user management moves to a new fn=usr
(IntranetController.AdminUsers.cs, fis.usr.js). Both are now reached
through a single "Administration" sidemenu dropdown (Systemüberwachung /
Benutzerverwaltung) gated by one shared fds_sys > 4 check, per ADR 0015
(supersedes 0014's "fold into one module" choice).

Also fixes the password-reset validation to check only a plain 6-char
minimum instead of silently also requiring ValidatePassword's default
2-uppercase/2-lowercase/2-digit composition, which contradicted the
"mind. 6 Zeichen" message shown to the admin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 12:09:44 +02:00
co-authored by Claude Sonnet 5
parent 93004904af
commit 076290cbeb
27 changed files with 951 additions and 598 deletions
-27
View File
@@ -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;
+1 -137
View File
@@ -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 ─────────────────────────────────────────── */
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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}
.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}
+18 -8
View File
@@ -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();
});
+1 -1
View File
File diff suppressed because one or more lines are too long
+68
View File
@@ -0,0 +1,68 @@
/* basics */
/* Color SCHEME */
/* #8396bd */
/* #e3e6e6 */
/* #ffffff */
/* #262626 */
/* #262626 */
/* #e3e6e6 */
/* #FFC801 */
/* #ffff4a */
/* media breaks */
/* other */
/* mixin */
/* basics */
/* Color SCHEME */
/* #1b4379 #0033b3 */
/*neu: #56a532 ; alt: rgb(32,144,119);*/
/* 10% sw */
.usrfrm > table {
border-collapse: collapse;
margin: 2rem;
}
.usrfrm > table th {
padding: 0.2rem 0.35rem;
border: 1px solid #CCC;
}
.usrfrm > table td {
padding: 0.2rem 0.35rem;
border: 1px solid #CCC;
}
.usrfrm > table tr {
cursor: pointer;
}
.usrfrm > table tr:nth-child(2n+1) td {
background-color: #EEE;
}
.usrfrm > table tr:hover td {
background-color: #f3f6fb;
}
.usrfrm > table tr.selected td {
background-color: lightblue;
}
.usrfrm .usr_err {
padding: 1rem 2rem;
color: #b00020;
font-weight: bold;
}
.usr_groups_dlg {
padding: 0.25rem 0;
}
.usr_groups_dlg .usr_group_row {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.35rem 0;
border-bottom: 1px solid #eee;
}
.usr_groups_dlg .usr_group_row:last-child {
border-bottom: none;
}
.usr_groups_dlg .usr_group_row input[type=checkbox] {
margin: 0;
}
.usr_groups_dlg .usr_group_row label {
margin: 0;
font-weight: normal;
}
+151
View File
@@ -0,0 +1,151 @@
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.'
};
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;
+1
View File
@@ -0,0 +1 @@
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."},$usr={init2:function(){$ocms.getScript([],(function(){$usr.init3()}))},init3:function(){$fis.cf(!0),$fis.lf(!0),$("#activemodule").text($uct.mdl),$usr.eM(),$usr.renderLst()},eM:function(e){let t=[];return!0===bool(e,!1)&&t.push({lbl:$uct.rel,fnc:$usr.renderLst}),$("#topbar").ocmsmenu(t)},iMn:function(e){return $("#topbar").ocmsmenu([{lbl:$uct.pwdreset,fnc:function(){$usr.pwdResetDlg(e)}},{lbl:$uct.email,fnc:function(){$usr.emailDlg(e)}},{lbl:$uct.groups,fnc:function(){$usr.groupsDlg(e)}}])},renderLst:function(){let e=$fis.frm_list(!0,!0).ldng(1),t=$$.dc("usrfrm",e);$usr.eM(!0),$ocms.postXT({url:$ocms.url("usr/list"),success:function(e){t.empty();let r=$$.tblset({},t),n=$$.tr(r.hd);$.each([$uct.col_name,$uct.col_email,$uct.col_status],(function(e,t){$$.th(n).text(t)})),$.each(e.users||[],(function(e,t){let n=$$.tr(r.bdy,{id:"usr"+t.useraccount_id});$$.td(n).text(`${t.firstname||""} ${t.name||""}`.trim()),$$.td(n).text(t.email||""),$$.td(n).text(!0===bool(t.disabled,!1)?$uct.disabled:$uct.active),n.click((function(){n.toggleClass("selected").siblings().rC("selected"),!0===n.is(".selected")?$usr.iMn(t):$usr.eM(!0)}))}))},error:function(){t.empty(),$$.dc("usr_err",t).text($uct.loadfail)},complete:function(){e.ldng(0)}})},pwdResetDlg:function(e){$ocms.dlgform([{name:"useraccount_id",type:"hidden",value:e.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 t=$(this).ldng(1),r=t.serializeObject(!0,{typedvalues:!0});$ocms.postXT({url:$ocms.url("usr/pwdreset"),data:r,success:function(e){alert(!0===e.emailed?$uct.pwdreset_sent:$uct.pwdreset_noemail),t.trigger("modal_close")},error:function(e){let t=((e||{}).responseJSON||{}).error;alert($uct.err[t]||$uct.err_generic)},complete:function(){t.ldng(0),$usr.renderLst()}})}})},emailDlg:function(e){$ocms.dlgform([{name:"useraccount_id",type:"hidden",value:e.useraccount_id},{name:"email",label:$uct.email_field,type:"email",required:!0,value:e.email}],{title:$uct.email,submit:function(e){let t=$(this).ldng(1),r=t.serializeObject(!0,{typedvalues:!0});$ocms.postXT({url:$ocms.url("usr/email"),data:r,success:function(){t.trigger("modal_close")},error:function(e){let t=((e||{}).responseJSON||{}).error;alert($uct.err[t]||$uct.err_generic)},complete:function(){t.ldng(0),$usr.renderLst()}})}})},groupsDlg:function(e){let t=$$.dc("usr_groups_dlg");$ocms.postXT({url:$ocms.url("usr/groups"),data:{useraccount_id:e.useraccount_id},success:function(r){$.each(r.groups||[],(function(r,n){let s=$$.dc("usr_group_row",t),u="usrg"+n.usergroup_id,i=$$.i({type:"checkbox",id:u}).prop("checked",bool(n.member,!1)).appendTo(s);$$.lbl(n.name,{for:u}).appendTo(s),i.change((function(){let t=i.prop("checked");$ocms.postXT({url:$ocms.url("usr/groupset"),data:{useraccount_id:e.useraccount_id,usergroup_id:n.usergroup_id,member:t},error:function(){i.prop("checked",!t),alert($uct.err_generic)}})}))}))}}),$ocms.dlg(t,{title:string($uct.groups_title,[`${e.firstname||""} ${e.name||""}`.trim()]),size:[420,380]})}},$$usr={init2:$usr.init2,auth:{}};export default $$usr;
+1
View File
@@ -0,0 +1 @@
.usrfrm>table{border-collapse:collapse;margin:2rem}.usrfrm>table td,.usrfrm>table th{padding:.2rem .35rem;border:1px solid #ccc}.usrfrm>table tr{cursor:pointer}.usrfrm>table tr:nth-child(2n+1) td{background-color:#eee}.usrfrm>table tr:hover td{background-color:#f3f6fb}.usrfrm>table tr.selected td{background-color:#add8e6}.usrfrm .usr_err{padding:1rem 2rem;color:#b00020;font-weight:700}.usr_groups_dlg{padding:.25rem 0}.usr_groups_dlg .usr_group_row{display:flex;align-items:center;gap:.5rem;padding:.35rem 0;border-bottom:1px solid #eee}.usr_groups_dlg .usr_group_row:last-child{border-bottom:none}.usr_groups_dlg .usr_group_row input[type=checkbox]{margin:0}.usr_groups_dlg .usr_group_row label{margin:0;font-weight:400}