11 KiB
status, lastUpdated, applyTo, relatedDecisions
| status | lastUpdated | applyTo | relatedDecisions | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Active | 2026-09-15 |
|
|
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)
- Menu visibility — at page load
fis_main_menu.js#addAdminMenuButtonsIfAuthorizedcalls$fis.getAuth('fds_sys')once (shared with the separateusruser-management module, see 0015); 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 alongsideusr's "Benutzerverwaltung" (init:usr) item — and re-render#mainmenu. Users below the threshold never see the dropdown and never fetch either module's script. Within the dropdown, "Systemüberwachung" (like "Spezialfunktionen") is ashift-onlyitem: it is listed only when the dropdown is opened with Shift held ($fis.bindShiftOnlyMenuItems); the/admindeep link is unaffected. This is a convenience/accident guard, not an authorization level. - Server-side gate — every
/do/admin/*endpoint resolvesfis_getModuleAuth('fds_sys', authuser)and returns 401 unless it is > 4. The only exception isadmin/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/attachpdf) → { 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,email,keyvault,blob,mfr) each return aSystemProbeResult(status=ok/error/disabled/unconfigured,ok,message,detail,durationMs, optionalmetrics). They never throw — failures are captured in the result.databaserunsSELECT @@SERVERNAME, DB_NAME(), SUSER_SNAME();emailcallsIComService.VerifyCredentialsAsync(GET{BaseUrl}/api/mailer/verifywith the configured Basic-auth account/token — see the Mailer API contract) and only turnsokwhen the Mailer API itself confirms the credentials (ok:true); whenProcessWebComSettings.ServerIdis configured it is sent along as&serverid=...so the API also confirms this server belongs to the account (an account/token that is valid but for a server the account doesn't own comes backok:falsewith the mismatchedserveridechoed, surfaced indetail); a configured-but-wrong token surfaces aserror, never as a false-positiveok— themailerEnabled/tokenConfiguredflags inGetInfo()describe configuration only and were never proof the credentials actually work;keyvaultreads the first managed secret via the DISecretClient;blobcallsIBlobStorageService.CheckConnectivityAsync(account-info request plus a per-container blob count for the invoice/reminder containers, surfaced asmetricsand totalled in the message);mfrcallsIMfrClientFactory.Create().GetEntities(). The genericmetrics(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, attachPdf)HTML-encodes the body and sends viaIComService, so theFuchs:Email:OverrideRecipientredirect applies exactly as for any other mail; the result reports the requested recipient and, when active, the override target. WithattachPdf(dialog select "PDF-Anhang mitsenden", default Ja; absent form value ⇒ on) a one-page letter PDF (Fuchs-Intranet-Testmail.pdf) is rendered in memory viaIPdfService.WriteLetterAsync+DocToPdfBytes— standard Fuchs letterhead, the recipient in the address window,Datum/Umgebung/Nummer=admin_testin the admin block, the mail's own subject and body as the letter text; no DB or stored document is touched. This exercises the Mailer's multipart/attachment path, so per ADR 0028sentis onlytruewhen the Mailer also confirmed the attachment (attachmentsProcessed == 1); the result'sattachmentNamesays whether an attachment was part of the send. A PDF rendering failure is reported in the result (sent=false, message names the error) and nothing is sent.- Every probe emits the
fuchs.systemstatus.probescounter tagged by component + status and ansystemstatus.probeactivity span.
Cards on the page
The page renders one non-probe system card, five refreshable connectivity cards (in this
order), and the non-refreshable startup-checks card. UI labels are from fis.admin_txt_de.js.
| Card (label) | Shows | Data source |
|---|---|---|
| System | Hostname, OS, .NET framework, Environment, Test-Deployment flag, Prozess-ID, Laufzeit/Start (UTC) | GetInfo() — Environment/Process.GetCurrentProcess(), no network call |
SQL-Datenbank (database) |
Server, Datenbank, Login (never the password) | Config: fuchs_fds_ConnectionString parsed. Probe: SELECT @@SERVERNAME, DB_NAME(), SUSER_SNAME() |
E-Mail-Dienst (email) |
Mailer aktiv, Base-URL, Account, Server-ID, Token hinterlegt, OverrideRecipient/Umleitung aktiv | Config: ProcessWebComSettings / FuchsEmailSettings. Probe: IComService.VerifyCredentialsAsync → GET {BaseUrl}/api/mailer/verify at ProcessWeb, ok only when the Mailer API confirms account (+ server-id when configured) |
Azure Blob Storage (blob) |
Aktiviert, Konfiguriert, Rechnungs-/Mahnungs-Container, per-container file counts (Metrics) | Config: AzureBlobStorageSettings / AzureBlobStorage_ConnectionString. Probe: IBlobStorageService.CheckConnectivityAsync (account-info + blob count per container) |
MFR (ERP) (mfr) |
Host, Zugangsdaten hinterlegt, Sync aktiv | Config: Fds:MFR_host / Fds:MFR_UserName / Fds:SyncEnabled. Probe: IMfrClientFactory.Create().GetEntities() |
Azure Key Vault (keyvault) |
Vault-URI, App-Präfix, Verwaltete Secrets (count), Client registriert | Config: SecretManagement:VaultUri/AppName/ManagedSecretKeys, whether a SecretClient is DI-registered. Probe: reads the first key in ManagedSecretKeys via SecretClient.GetSecretAsync |
| Startup-Prüfungen (non-refreshable) | OK/Fehler/übersprungen per check: Key Vault, SQL-Datenbank, MFR, Spire.PDF-Lizenz, E-Mail (Startup-Probe) | StartupCheckReporter.Latest, captured once by StartupSelfTestService at boot; see next section |
Each connectivity card also carries a colored status pill (ok/error/disabled/unconfigured)
and a per-card "Aktualisieren" button (POST /do/admin/probe/<component>); the topbar "Alle
prüfen" button re-runs all five in parallel via POST /do/admin/status. The E-Mail-Dienst card
additionally hosts the test-email dialog (see SendTestEmailAsync below).
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.
Key files
Fuchs/Controllers/IntranetController.Admin.cs—Do_Process_Admindispatch + shared auth gate/camelCase JSON helper (also used by the separateusrmodule, see 0015).Fuchs/Services/ISystemStatusService.cs,SystemStatusService.cs,SystemStatusModels.cs— diagnostics service + DTOs.Fuchs/Services/StartupCheckReport.cs(StartupCheckReportersingleton) +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 viabdlconfig.json).Fuchs/js/intranet/fis_main_menu.js(addAdminMenuButtonsIfAuthorized) +fis_main_go.js— conditional menu buttons (this module andusr).Fuchs.Tests/SystemStatusServiceTests.cs— service tests.
User account/group management moved to its own module and concept doc — see admin-user-management.md.