Split Admin module into Status and User Management under one sidemenu dropdown
Playwright Tests / test (push) Canceled after 0s
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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user