--- status: Active lastUpdated: 2026-07-16 applyTo: - "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" relatedDecisions: - "0011-admin-module-system-status.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/ → { 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. ## Key files - `Fuchs/Controllers/IntranetController.Admin.cs` — `Do_Process_Admin` dispatch + auth gate + camelCase JSON helper. - `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.Tests/SystemStatusServiceTests.cs` — service tests. ## Related decisions - [0011 — Admin module gated on `fds_sys` > 4](../Decisions/0011-admin-module-system-status.md)