Files
Fuchs_Intranet/Fuchs/Docs/Concepts/admin-system-status.md
T
StefanandClaude Sonnet 5 a5f497090e
Playwright Tests / test (push) Canceled after 0s
Add Administration user management: list, password reset, email change, group assignments
Wires the previously-unused fis_admin_* user/group stored procs into the Admin
module (fds_sys-gated), adds a users grid with a per-row ribbon (reset
password, change email, manage group membership), and always emails a
password reset result to the user rather than showing it to the admin.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 10:34:04 +02:00

8.9 KiB

status, lastUpdated, applyTo, relatedDecisions
status lastUpdated applyTo relatedDecisions
Active 2026-09-09
Fuchs/Controllers/IntranetController.Admin.cs
Fuchs/Services/SystemStatusService.cs
Fuchs/Services/ISystemStatusService.cs
Fuchs/Services/SystemStatusModels.cs
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
0011-admin-module-system-status.md
0014-admin-user-management.md

Admin / System-Status module

Summary

The Admin module gives a privileged operator a live, in-app view of the running deployment's configuration and health, plus a test-email tool. It answers "which host am I on, can this instance reach SQL Server / Key Vault / Blob Storage / the MFR ERP, how is email wired up (including the OverrideRecipient redirect), and does sending actually work". Access is restricted to users whose fds_sys module authorization is greater than 4.

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.
  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 frontend can cleanly decline to render. This is defense in depth: hiding the button is not a security control on its own.

Request flow

click "Administration"  → $ocms.init('admin')
  POST /do/admin/auth   → { manage, level }         (manage>0 ⇒ authorized)
  load /web/fis.admin.de.js + /web/fis.admin.css
  $ocms.admin.init2()   → init3() renders the page
    POST /do/admin/status → { info, probes }         full snapshot + all probes
  per-card "Aktualisieren" → POST /do/admin/probe/<component> → { probe }
  "Test-E-Mail senden"     → POST /do/admin/testmail (to/subject/body) → { result }

Backend (ISystemStatusService / SystemStatusService, scoped)

  • GetInfo() — passive snapshot, no network calls: host (machine/OS/framework/environment/ test-deployment/uptime), database (server/catalog/login parsed from the connection string — never the password), email (mailer enabled/base-url/account/server-id/token-present + OverrideRecipient), blob (enabled/configured/containers), MFR (host/creds-present/sync), Key Vault (vault-uri/app-prefix/managed-secret-count/client-registered).
  • Probes (database, keyvault, blob, mfr) each return a SystemProbeResult (status = ok/error/disabled/unconfigured, ok, message, detail, durationMs, optional metrics). They never throw — failures are captured in the result. database runs SELECT @@SERVERNAME, DB_NAME(), SUSER_SNAME(); keyvault reads the first managed secret via the DI SecretClient; blob calls IBlobStorageService.CheckConnectivityAsync (account-info request plus a per-container blob count for the invoice/reminder containers, surfaced as metrics and totalled in the message); mfr calls IMfrClientFactory.Create().GetEntities(). The generic metrics (label/value pairs) is how a probe reports extra facts for display — the blob probe uses it for the file counts.
  • SendTestEmailAsync(to, subject, body) HTML-encodes the body and sends via IComService, so the Fuchs:Email:OverrideRecipient redirect applies exactly as for any other mail; the result reports the requested recipient and, when active, the override target.
  • Every probe emits the fuchs.systemstatus.probes counter tagged by component + status and an systemstatus.probe activity span.

Startup-checks widget (non-refreshable)

StartupSelfTestService runs the boot self-test once (Key Vault / Database / MFR / PDF-license / mailer, gated by Fuchs:StartupChecks:*). It now also writes its outcome to the singleton StartupCheckReporter, which GetInfo() returns as StartupChecks. The Admin page renders it as a single non-refreshable card (there is no per-check retry — the live connectivity probes above cover on-demand re-testing; the startup card is a historical record of the boot run). When the self-test is disabled (Enabled=false, the appsettings default) or has not completed, the card shows a "nicht ausgeführt" note. Each item shows OK / Fehler / übersprungen (a disabled check reports enabled=false).

Frontend (fis.admin.js + fis.admin_txt_de.js + fis.admin.scss)

Standard lazy-loaded module (same contract as inv/rep/bam). Renders a system card plus a responsive grid of status cards; connectivity cards carry a colored status pill and a per-card refresh button, the email card carries the test-email dialog. The topbar has an "Alle prüfen" 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 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/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.Tests/SystemStatusServiceTests.cs — service tests.
  • Fuchs.Tests/AdminUserManagementTests.cs — password-reset email content tests.