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
-111
View File
@@ -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 ─────────────────────────────────────────── */