feat(portal-admin): audit log viewer screen #136
Reference in New Issue
Block a user
Delete Branch "feat/portal-admin-audit-viewer"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Final piece of the portal-admin chantier per ADR-0020 §"v1 scope" item 4. The new
/auditroute consumesGET /api/admin/audit(PR #132), renders a filter form + paginated results table, and trips theadmin.audit.querydeterrent on every fetch. Closes the loop from "BFF emits audit events" (PRs #120, #127, #128) through "BFF exposes them via a guarded endpoint" (PR #132) to "an admin can actually read them in a browser".What lands
AuditEventsServiceThin
HttpClientwrapper aroundGET /api/admin/audit. BuildsHttpParamsfrom the filter shape, dropping empty strings (Nest'sValidationPipetreats?foo=asfoo === ''and 400s).providedIn: 'root'— the audit page is the only consumer in v1.AuditPageSignal-driven page. State surface:
eventType,actorIdHash,audience,outcome,subjectPrefix,createdAtFrom,createdAtTo[ngModel]/(ngModelChange).limit,offsetlimitdefaults to 50, capped at 200 to mirror the BFF'sMAX_LIMIT.page,loading,errorhasNextPage,hasPreviousPage,resultRangeUI structure:
Date.toLocaleString), event type (monospaced), audience + outcome with color-coded badges (successgreen /failurered /deniedamber), actor hash + subject stacked, trace id, and a<details>disclosure for the JSON payload so the row stays scannable."No audit events match the current filters."), error message. 403 surfaces "you do not have access"; everything else collapses to a generic retry message so the admin UI doesn't leak BFF internals.Routing + i18n
app.routes.ts—/auditlazy-loaded.Audit log — APF Portal Admin(EN) /Journal d'audit — Administration APF Portal(FR). The matchingroute.audit.titletrans-unit lands inmessages.fr.xlf— required because the admin app's prod build usesi18nMissingTranslation: "error".Implementation notes
@RequireMfanot applied to the BFF endpoint in v1. The admin surface already sits behind a freshly-MFA'd session (/api/admin/auth/loginenforces it via Entra CA), and the per-queryadmin.audit.queryaudit row is the deterrent against fishing expeditions. Adding@RequireMfa({ freshness: 600 })is a one-line change when the security review asks — the SPA'sbffUnauthorizedInterceptoralready handles the resulting 401 gracefully.AuditReader.findEventsclampslimitto 200 regardless of what comes over the wire — defense in depth.AdminAuditQueryinterface uses?: T | undefinedso callers can build the query object withundefinedplaceholders underexactOptionalPropertyTypes: true— the page'sbuildFilters()does exactly that.font-familystacks and unusedtext-transform/letter-spacingdeclarations to fit.Test plan
pnpm nx test portal-admin— 30 specs pass (was 15; +15: AuditEventsService 3, AuditPage 12).pnpm exec nx affected -t format:check lint test build --base=origin/main— clean.HttpParams, drop empty strings, omitundefined./api/admin/auth/login(requires the Entraadminapp role assignment), navigate to/audit, expect the most recentauth.sign_in/admin.audit.queryevents from earlier sessions, exercise the filter form, observe a newadmin.audit.queryrow inaudit.eventsper fetch.Notes for the reviewer
<details>rather than a modal or always-on JSON viewer — the table stays scannable at glance, and an auditor pivoting into a specific row gets the structured detail one click away. No third-party JSON-tree dependency added in v1.aria-disabledplaceholder until now; this PR makes it live. The other three v1 modules (CMS, menu management, user list) remain placeholders and will graduate as they land.What's next
This PR closes the portal-admin chantier. Open follow-ups from the roadmap:
DownstreamApiClient+ OBO (ADR-0014) — first real consumer when an Entra-protected business API needs the BFF as a passthrough.