Files
Fuchs_Intranet/Fuchs/Docs/Concepts/admin-system-status.md
T
Stefan 9569c1b65e
Playwright Tests / test (push) Canceled after 0s
feat: Enhance invoice file backup procedure to include JSON snapshots
- Updated the stored procedure `fds__admin_backupAndClearInvoiceFile.sql` to back up not only the invoice file but also JSON snapshots of related data (invoices, items, details, service requests) to ensure consistency and traceability.
- Modified the table structure of `fds__invoice_file_backups` to accommodate new JSON columns for storing these snapshots.
- Added an ADR (0030) document to outline the decision and rationale behind these changes, emphasizing the importance of maintaining a consistent set of data alongside the backed-up file.
2026-09-17 22:39:01 +02:00

127 lines
8.6 KiB
Markdown

---
status: Active
lastUpdated: 2026-09-15
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"
- "0015-split-admin-user-management-module.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#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. Within the dropdown, "Systemüberwachung"
(like "Spezialfunktionen") is a `shift-only` item: it is listed only when the dropdown is
opened with **Shift** held (`$fis.bindShiftOnlyMenuItems`); the `/admin` deep link is
unaffected. This is a convenience/accident guard, not an authorization level.
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/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 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()`; `email` calls
`IComService.VerifyCredentialsAsync` (GET `{BaseUrl}/api/mailer/verify` with the configured
Basic-auth account/token — see the Mailer API contract) and only turns `ok` when the Mailer API
itself confirms the credentials (`ok:true`); when `ProcessWebComSettings.ServerId` is 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 back `ok:false`
with the mismatched `serverid` echoed, surfaced in `detail`); a configured-but-wrong token
surfaces as `error`, never as a false-positive `ok` — the `mailerEnabled`/`tokenConfigured` flags
in `GetInfo()` describe configuration only and were never proof the credentials actually work;
`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, attachPdf)` 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.
With `attachPdf` (dialog select "PDF-Anhang mitsenden", default *Ja*; absent form value ⇒ on)
a one-page letter PDF (`Fuchs-Intranet-Testmail.pdf`) is rendered in memory via
`IPdfService.WriteLetterAsync` + `DocToPdfBytes` — standard Fuchs letterhead, the recipient in
the address window, `Datum`/`Umgebung`/`Nummer=admin_test` in 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 0028 `sent` is only `true` when the Mailer also
confirmed the attachment (`attachmentsProcessed == 1`); the result's `attachmentName` says
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.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 + 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` (`addAdminMenuButtonsIfAuthorized`) + `fis_main_go.js` — conditional menu buttons (this module and `usr`).
- `Fuchs.Tests/SystemStatusServiceTests.cs` — service 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)
- [0015 — Split Admin into separate Status/User-Management modules](../Decisions/0015-split-admin-user-management-module.md)