Migrate Audit evidence interfaces

This commit is contained in:
2026-08-03 10:43:24 +02:00
parent 4177287b22
commit 6d3fcc1572
9 changed files with 268 additions and 25 deletions
+4
View File
@@ -26,3 +26,7 @@ foundation:
See [docs/AUDIT_TRACE_CONTEXT.md](docs/AUDIT_TRACE_CONTEXT.md) for the standard See [docs/AUDIT_TRACE_CONTEXT.md](docs/AUDIT_TRACE_CONTEXT.md) for the standard
operational context fields used by admin, installer, and module lifecycle audit operational context fields used by admin, installer, and module lifecycle audit
entries. entries.
The administration surface archetypes, consequence classification, and
verification contract are recorded in
[docs/INTERFACE_PATTERN_MIGRATION.md](docs/INTERFACE_PATTERN_MIGRATION.md).
+40
View File
@@ -0,0 +1,40 @@
# Audit Interface Pattern Migration
Audit contributes two read-only administration surfaces through the shared
`admin.sections` capability. Both use the platform's monitoring and evidence
archetype.
## Surface Map
| Surface | Authority | Pattern | Consequence class |
| --- | --- | --- | --- |
| `audit.admin.system` | `system:audit:read` | Server-filtered evidence grid and event inspector | Read-only evidence disclosure |
| `audit.admin.tenant` | `audit:read` for the active tenant | Server-filtered evidence grid and event inspector | Read-only evidence disclosure |
Audit does not expose mutation or destructive actions in these panels. The
only row action opens an inspection dialog; reload preserves the stable shell
and existing evidence while a newer projection is requested.
## Interaction Contract
- Core owns the admin layout, DataGrid, dialog, action group, loading/error
treatment, disabled-action explanation, and documentation link.
- Filtering, sorting, counts, and paging are server-owned. The first page may
apply bounded delta updates using an opaque watermark; a full response
remains authoritative when the delta contract cannot be used.
- System and tenant panels remain distinct and are registered only with their
respective read scopes. Tenant selection is never accepted as a free-form
client override.
- The event inspector renders stable actor, action, object, tenant, timestamp,
and structured detail rows. It does not add editing, replay, export, or raw
credential access.
- Contextual help resolves through `audit.read-authorized-evidence`; operational
recording, retention, and outbox guidance remains in the separate admin
topic.
## Verification
Run the Audit backend suite and `npm run test:interface-patterns` in `webui`.
The structural test guards shared components, localized labels, contextual
help, server paging, readable detail projection, and absence of private sibling
imports or browser-native dialogs.
+17 -2
View File
@@ -64,7 +64,15 @@ manifest = ModuleManifest(
body="Audit views are permission- and tenant-scoped. Entries are evidence, not editable business records. Sensitive payloads may be redacted while stable resource, actor, outcome, request, run, and trace references remain available for investigation.", body="Audit views are permission- and tenant-scoped. Entries are evidence, not editable business records. Sensitive payloads may be redacted while stable resource, actor, outcome, request, run, and trace references remain available for investigation.",
documentation_types=("user",), documentation_types=("user",),
audience=("auditor", "tenant_admin", "operator"), audience=("auditor", "tenant_admin", "operator"),
metadata={"kind": "reference"}, metadata={
"kind": "reference",
"help_contexts": [
"audit.admin.system",
"audit.admin.tenant",
"audit.event-details",
],
"surfaces": ["audit.admin.system", "audit.admin.tenant"],
},
), ),
DocumentationTopic( DocumentationTopic(
id="audit.recording-retention-and-outbox", id="audit.recording-retention-and-outbox",
@@ -74,7 +82,14 @@ manifest = ModuleManifest(
documentation_types=("admin",), documentation_types=("admin",),
audience=("auditor", "security_officer", "operator"), audience=("auditor", "security_officer", "operator"),
related_modules=("policy", "ops"), related_modules=("policy", "ops"),
metadata={"kind": "reference"}, metadata={
"kind": "reference",
"help_contexts": [
"audit.recording",
"audit.retention",
"audit.event-outbox",
],
},
), ),
), ),
frontend=FrontendModule( frontend=FrontendModule(
+15
View File
@@ -4,6 +4,8 @@ import pathlib
import tomllib import tomllib
import unittest import unittest
from govoplan_audit.backend.manifest import get_manifest
ROOT = pathlib.Path(__file__).resolve().parents[1] ROOT = pathlib.Path(__file__).resolve().parents[1]
@@ -25,6 +27,19 @@ class AuditModuleContractTests(unittest.TestCase):
self.assertEqual([], offenders) self.assertEqual([], offenders)
def test_audit_documentation_declares_admin_help_contexts(self) -> None:
topics = {topic.id: topic for topic in get_manifest().documentation}
evidence = topics["audit.read-authorized-evidence"]
self.assertIn("audit.admin.system", evidence.metadata["help_contexts"])
self.assertIn("audit.admin.tenant", evidence.metadata["help_contexts"])
self.assertEqual(
["audit.admin.system", "audit.admin.tenant"],
evidence.metadata["surfaces"],
)
operations = topics["audit.recording-retention-and-outbox"]
self.assertIn("audit.retention", operations.metadata["help_contexts"])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+3
View File
@@ -12,6 +12,9 @@
"import": "./src/index.ts" "import": "./src/index.ts"
} }
}, },
"scripts": {
"test:interface-patterns": "node scripts/test-interface-pattern-language.mjs"
},
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.9", "@govoplan/core-webui": "^0.1.9",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
@@ -0,0 +1,29 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
const panel = readFileSync(
fileURLToPath(new URL("../src/features/audit/AdminAuditPanel.tsx", import.meta.url)),
"utf8"
);
const moduleSource = readFileSync(
fileURLToPath(new URL("../src/module.ts", import.meta.url)),
"utf8"
);
assert.match(panel, /AdminPageLayout,[\s\S]*DataGrid,[\s\S]*Dialog,[\s\S]*DocumentationHelpLink,[\s\S]*TableActionGroup/);
assert.match(panel, /topicId: "audit\.read-authorized-evidence"/);
assert.match(panel, /disabledReason=\{loading \? I18N\.loading : undefined\}/);
assert.match(panel, /pagination=\{\{[\s\S]*mode: "server"/);
assert.match(panel, /id="admin-audit-event-details-grid"/);
assert.match(panel, /auditDetailRows\(selected\?\.details \?\? \{\}\)/);
assert.match(panel, /emptyText=\{I18N\.noRecords\}/);
assert.doesNotMatch(panel, /<pre|window\.(?:alert|confirm)\(/);
assert.doesNotMatch(panel, /@govoplan\/(?:access|admin)-webui|govoplan_(?:access|admin)/);
assert.match(moduleSource, /generatedTranslations/);
assert.match(moduleSource, /translations,/);
assert.match(moduleSource, /surfaceId: "audit\.admin\.system"[\s\S]*allOf: \["system:audit:read"\]/);
assert.match(moduleSource, /surfaceId: "audit\.admin\.tenant"[\s\S]*allOf: \["audit:read"\]/);
console.log("Audit administration surfaces satisfy the monitoring and evidence pattern contracts.");
+101 -20
View File
@@ -6,7 +6,9 @@ import {
Button, Button,
DataGrid, DataGrid,
Dialog, Dialog,
DocumentationHelpLink,
formatAdminDateTime as formatDateTime, formatAdminDateTime as formatDateTime,
i18nMessage,
mergeDeltaRows, mergeDeltaRows,
TableActionGroup, TableActionGroup,
useDeltaWatermarks, useDeltaWatermarks,
@@ -22,6 +24,35 @@ type Props = {
auth: AuthInfo; auth: AuthInfo;
systemMode?: boolean; systemMode?: boolean;
}; };
type AuditDetailRow = {
id: string;
field: string;
value: string;
};
const I18N = {
actionLabel: "i18n:govoplan-audit.action.f1a20801",
actions: "i18n:govoplan-audit.actions.f1a20802",
actor: "i18n:govoplan-audit.actor.f1a20803",
close: "i18n:govoplan-audit.close.f1a20804",
details: "i18n:govoplan-audit.details.f1a20805",
eventDetails: "i18n:govoplan-audit.audit_event_details.f1a20806",
inspect: "i18n:govoplan-audit.inspect_audit_event.f1a20807",
loading: "i18n:govoplan-audit.audit_evidence_is_loading.f1a20808",
noDetails: "i18n:govoplan-audit.no_additional_details_were_recorded.f1a20809",
noRecords: "i18n:govoplan-audit.no_audit_records_match_the_current_scope_and_filters.f1a20810",
object: "i18n:govoplan-audit.object.f1a20811",
reload: "i18n:govoplan-audit.reload_audit_evidence.f1a20812",
scopeLabel: "i18n:govoplan-audit.scope.f1a20813",
system: "i18n:govoplan-audit.system.f1a20814",
systemAudit: "i18n:govoplan-audit.system_audit.f1a20815",
systemDescription: "i18n:govoplan-audit.system_level_administrative_history_showing_value0_value1_of_value2.f1a20816",
tenantAudit: "i18n:govoplan-audit.tenant_audit.f1a20817",
tenantContext: "i18n:govoplan-audit.tenant_context.f1a20818",
tenantDescription: "i18n:govoplan-audit.tenant_level_administrative_history_showing_value0_value1_of_value2.f1a20819",
time: "i18n:govoplan-audit.time.f1a20820",
value: "i18n:govoplan-audit.value.f1a20821"
} as const;
const DEFAULT_QUERY: DataGridQueryState = { const DEFAULT_QUERY: DataGridQueryState = {
sort: { columnId: "time", direction: "desc" }, sort: { columnId: "time", direction: "desc" },
@@ -113,28 +144,49 @@ export default function AdminAuditPanel({ settings, auth, systemMode = false }:
}, []); }, []);
const columns = useMemo<DataGridColumn<AuditAdminItem>[]>(() => [ const columns = useMemo<DataGridColumn<AuditAdminItem>[]>(() => [
{ id: "time", header: "Time", width: 190, minWidth: 150, maxWidth: 260, resizable: true, sticky: "start", sortable: true, filterable: true, filterType: "date", value: (row) => row.created_at, render: (row) => formatDateTime(row.created_at) }, { id: "time", header: I18N.time, width: 190, minWidth: 150, maxWidth: 260, resizable: true, sticky: "start", sortable: true, filterable: true, filterType: "date", value: (row) => row.created_at, render: (row) => formatDateTime(row.created_at) },
{ id: "actor", header: "Actor", width: 220, minWidth: 170, maxWidth: 360, resizable: true, sortable: true, filterable: true, value: (row) => row.actor_email || "System" }, { id: "actor", header: I18N.actor, width: 220, minWidth: 170, maxWidth: 360, resizable: true, sortable: true, filterable: true, value: (row) => row.actor_email || "System", render: (row) => row.actor_email || I18N.system },
{ id: "action", header: "Action", width: 250, minWidth: 170, maxWidth: 420, resizable: true, sortable: true, filterable: true, value: (row) => row.action }, { id: "action", header: I18N.actionLabel, width: 250, minWidth: 170, maxWidth: 420, resizable: true, sortable: true, filterable: true, value: (row) => row.action },
{ id: "object", header: "Object", width: 300, minWidth: 180, maxWidth: 640, resizable: true, fill: true, sortable: true, filterable: true, value: (row) => `${row.object_type || "-"} ${row.object_id || ""}`.trim() }, { id: "object", header: I18N.object, width: 300, minWidth: 180, maxWidth: 640, resizable: true, fill: true, sortable: true, filterable: true, value: (row) => `${row.object_type || "-"} ${row.object_id || ""}`.trim() },
...(systemMode ? [{ id: "tenant", header: "Tenant context", width: 190, minWidth: 150, maxWidth: 300, resizable: true, sortable: true, filterable: true, value: (row: AuditAdminItem) => row.tenant_id || "-" }] : []), ...(systemMode ? [{ id: "tenant", header: I18N.tenantContext, width: 190, minWidth: 150, maxWidth: 300, resizable: true, sortable: true, filterable: true, value: (row: AuditAdminItem) => row.tenant_id || "-" }] : []),
{ id: "actions", header: "Actions", width: 70, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "inspect", label: "Inspect audit event", icon: <Search />, onClick: () => setSelected(row) }]} /> } { id: "actions", header: I18N.actions, width: 70, sticky: "end", resizable: false, align: "right", render: (row) => <TableActionGroup actions={[{ id: "inspect", label: I18N.inspect, icon: <Search aria-hidden="true" size={16} />, onClick: () => setSelected(row) }]} /> }
], [systemMode]); ], [systemMode]);
const detailColumns = useMemo<DataGridColumn<AuditDetailRow>[]>(() => [
{ id: "field", header: I18N.details, minWidth: 180, resizable: true, value: (row) => row.field },
{ id: "value", header: I18N.value, minWidth: 260, resizable: true, fill: true, value: (row) => row.value }
], []);
const detailRows = useMemo(() => auditDetailRows(selected?.details ?? {}), [selected]);
const firstShown = total === 0 ? 0 : (page - 1) * pageSize + 1; const firstShown = total === 0 ? 0 : (page - 1) * pageSize + 1;
const lastShown = Math.min(total, page * pageSize); const lastShown = Math.min(total, page * pageSize);
const pageDescription = systemMode const pageDescription = i18nMessage(
? `System-level administrative history, showing ${firstShown}-${lastShown} of ${total}.` systemMode ? I18N.systemDescription : I18N.tenantDescription,
: `Tenant-level administrative history for the active tenant, showing ${firstShown}-${lastShown} of ${total}.`; { value0: firstShown, value1: lastShown, value2: total }
);
return ( return (
<> <>
<AdminPageLayout <AdminPageLayout
title={systemMode ? "System audit" : "Tenant audit"} title={systemMode ? I18N.systemAudit : I18N.tenantAudit}
description={pageDescription} description={pageDescription}
loading={loading} loading={loading}
error={error} error={error}
actions={<Button onClick={() => setReloadToken((value) => value + 1)} disabled={loading}>Reload</Button>}> actions={(
<>
<DocumentationHelpLink
reference={{
topicId: "audit.read-authorized-evidence",
documentationType: "user"
}} />
<Button
onClick={() => setReloadToken((value) => value + 1)}
disabled={loading}
disabledReason={loading ? I18N.loading : undefined}>
{I18N.reload}
</Button>
</>
)}>
<div className="admin-table-surface"> <div className="admin-table-surface">
<DataGrid <DataGrid
id={systemMode ? "admin-system-audit-v6" : "admin-tenant-audit-v6"} id={systemMode ? "admin-system-audit-v6" : "admin-tenant-audit-v6"}
@@ -142,7 +194,7 @@ export default function AdminAuditPanel({ settings, auth, systemMode = false }:
columns={columns} columns={columns}
initialFit="container" initialFit="container"
getRowKey={(row) => row.id} getRowKey={(row) => row.id}
emptyText="No administrative audit records found." emptyText={I18N.noRecords}
className="admin-audit-grid" className="admin-audit-grid"
initialSort={{ columnId: "time", direction: "desc" }} initialSort={{ columnId: "time", direction: "desc" }}
pagination={{ pagination={{
@@ -159,18 +211,30 @@ export default function AdminAuditPanel({ settings, auth, systemMode = false }:
/> />
</div> </div>
</AdminPageLayout> </AdminPageLayout>
<Dialog open={Boolean(selected)} title="Audit event details" onClose={() => setSelected(null)} className="admin-dialog admin-dialog-wide" footer={<Button onClick={() => setSelected(null)}>Close</Button>}> <Dialog
open={Boolean(selected)}
title={I18N.eventDetails}
onClose={() => setSelected(null)}
className="admin-dialog admin-dialog-wide"
footer={<Button onClick={() => setSelected(null)}>{I18N.close}</Button>}>
{selected && ( {selected && (
<> <>
<dl className="admin-details-grid"> <dl className="admin-details-grid">
<div><dt>Scope</dt><dd>{selected.scope}</dd></div> <div><dt>{I18N.scopeLabel}</dt><dd>{selected.scope}</dd></div>
<div><dt>Action</dt><dd>{selected.action}</dd></div> <div><dt>{I18N.actionLabel}</dt><dd>{selected.action}</dd></div>
<div><dt>Actor</dt><dd>{selected.actor_email || "System"}</dd></div> <div><dt>{I18N.actor}</dt><dd>{selected.actor_email || I18N.system}</dd></div>
<div><dt>Object</dt><dd>{selected.object_type || "-"} {selected.object_id || ""}</dd></div> <div><dt>{I18N.object}</dt><dd>{selected.object_type || "-"} {selected.object_id || ""}</dd></div>
<div><dt>Tenant context</dt><dd>{selected.tenant_id || "-"}</dd></div> <div><dt>{I18N.tenantContext}</dt><dd>{selected.tenant_id || "-"}</dd></div>
<div><dt>Time</dt><dd>{formatDateTime(selected.created_at)}</dd></div> <div><dt>{I18N.time}</dt><dd>{formatDateTime(selected.created_at)}</dd></div>
</dl> </dl>
<pre className="admin-json-preview">{JSON.stringify(selected.details, null, 2)}</pre> {detailRows.length ? (
<DataGrid
id="admin-audit-event-details-grid"
rows={detailRows}
columns={detailColumns}
getRowKey={(row) => row.id}
initialFit="container" />
) : <p className="muted">{I18N.noDetails}</p>}
</> </>
)} )}
</Dialog> </Dialog>
@@ -178,6 +242,23 @@ export default function AdminAuditPanel({ settings, auth, systemMode = false }:
); );
} }
function auditDetailRows(details: Record<string, unknown>): AuditDetailRow[] {
return Object.entries(details)
.sort(([left], [right]) => left.localeCompare(right))
.map(([field, value]) => ({
id: field,
field,
value: auditDetailValue(value)
}));
}
function auditDetailValue(value: unknown): string {
if (value === null || value === undefined) return "-";
if (typeof value === "string") return value;
if (typeof value === "number" || typeof value === "boolean") return String(value);
return JSON.stringify(value);
}
function compareAuditEvents(sortBy: AuditSortBy, sortDirection: "asc" | "desc"): (left: AuditAdminItem, right: AuditAdminItem) => number { function compareAuditEvents(sortBy: AuditSortBy, sortDirection: "asc" | "desc"): (left: AuditAdminItem, right: AuditAdminItem) => number {
return (left, right) => { return (left, right) => {
const primary = compareAuditValues(auditSortValue(left, sortBy), auditSortValue(right, sortBy)); const primary = compareAuditValues(auditSortValue(left, sortBy), auditSortValue(right, sortBy));
+50
View File
@@ -0,0 +1,50 @@
import type { PlatformTranslations } from "@govoplan/core-webui";
export const generatedTranslations: PlatformTranslations = {
en: {
"i18n:govoplan-audit.action.f1a20801": "Action",
"i18n:govoplan-audit.actions.f1a20802": "Actions",
"i18n:govoplan-audit.actor.f1a20803": "Actor",
"i18n:govoplan-audit.close.f1a20804": "Close",
"i18n:govoplan-audit.details.f1a20805": "Detail",
"i18n:govoplan-audit.audit_event_details.f1a20806": "Audit event details",
"i18n:govoplan-audit.inspect_audit_event.f1a20807": "Inspect audit event",
"i18n:govoplan-audit.audit_evidence_is_loading.f1a20808": "Audit evidence is loading.",
"i18n:govoplan-audit.no_additional_details_were_recorded.f1a20809": "No additional details were recorded.",
"i18n:govoplan-audit.no_audit_records_match_the_current_scope_and_filters.f1a20810": "No audit records match the current scope and filters.",
"i18n:govoplan-audit.object.f1a20811": "Object",
"i18n:govoplan-audit.reload_audit_evidence.f1a20812": "Reload audit evidence",
"i18n:govoplan-audit.scope.f1a20813": "Scope",
"i18n:govoplan-audit.system.f1a20814": "System",
"i18n:govoplan-audit.system_audit.f1a20815": "System audit",
"i18n:govoplan-audit.system_level_administrative_history_showing_value0_value1_of_value2.f1a20816": "System-level administrative history, showing {value0}-{value1} of {value2}.",
"i18n:govoplan-audit.tenant_audit.f1a20817": "Tenant audit",
"i18n:govoplan-audit.tenant_context.f1a20818": "Tenant context",
"i18n:govoplan-audit.tenant_level_administrative_history_showing_value0_value1_of_value2.f1a20819": "Tenant-level administrative history for the active tenant, showing {value0}-{value1} of {value2}.",
"i18n:govoplan-audit.time.f1a20820": "Time",
"i18n:govoplan-audit.value.f1a20821": "Value"
},
de: {
"i18n:govoplan-audit.action.f1a20801": "Aktion",
"i18n:govoplan-audit.actions.f1a20802": "Aktionen",
"i18n:govoplan-audit.actor.f1a20803": "Akteur",
"i18n:govoplan-audit.close.f1a20804": "Schließen",
"i18n:govoplan-audit.details.f1a20805": "Detail",
"i18n:govoplan-audit.audit_event_details.f1a20806": "Details des Auditereignisses",
"i18n:govoplan-audit.inspect_audit_event.f1a20807": "Auditereignis prüfen",
"i18n:govoplan-audit.audit_evidence_is_loading.f1a20808": "Auditnachweise werden geladen.",
"i18n:govoplan-audit.no_additional_details_were_recorded.f1a20809": "Es wurden keine zusätzlichen Details aufgezeichnet.",
"i18n:govoplan-audit.no_audit_records_match_the_current_scope_and_filters.f1a20810": "Keine Auditaufzeichnungen entsprechen dem aktuellen Bereich und den Filtern.",
"i18n:govoplan-audit.object.f1a20811": "Objekt",
"i18n:govoplan-audit.reload_audit_evidence.f1a20812": "Auditnachweise neu laden",
"i18n:govoplan-audit.scope.f1a20813": "Geltungsbereich",
"i18n:govoplan-audit.system.f1a20814": "System",
"i18n:govoplan-audit.system_audit.f1a20815": "Systemaudit",
"i18n:govoplan-audit.system_level_administrative_history_showing_value0_value1_of_value2.f1a20816": "Systemweite administrative Historie, angezeigt werden {value0}-{value1} von {value2}.",
"i18n:govoplan-audit.tenant_audit.f1a20817": "Mandantenaudit",
"i18n:govoplan-audit.tenant_context.f1a20818": "Mandantenkontext",
"i18n:govoplan-audit.tenant_level_administrative_history_showing_value0_value1_of_value2.f1a20819": "Administrative Historie des aktiven Mandanten, angezeigt werden {value0}-{value1} von {value2}.",
"i18n:govoplan-audit.time.f1a20820": "Zeit",
"i18n:govoplan-audit.value.f1a20821": "Wert"
}
};
+9 -3
View File
@@ -1,7 +1,12 @@
import { createElement, lazy } from "react"; import { createElement, lazy } from "react";
import { type AdminSectionsUiCapability, type PlatformWebModule } from "@govoplan/core-webui"; import { type AdminSectionsUiCapability, type PlatformWebModule } from "@govoplan/core-webui";
import { generatedTranslations } from "./i18n/generatedTranslations";
const AdminAuditPanel = lazy(() => import("./features/audit/AdminAuditPanel")); const AdminAuditPanel = lazy(() => import("./features/audit/AdminAuditPanel"));
const translations = {
en: generatedTranslations.en,
de: generatedTranslations.de
};
const auditAdminSections: AdminSectionsUiCapability = { const auditAdminSections: AdminSectionsUiCapability = {
sections: [ sections: [
@@ -10,7 +15,7 @@ const auditAdminSections: AdminSectionsUiCapability = {
moduleId: "audit", moduleId: "audit",
kind: "management", kind: "management",
surfaceId: "audit.admin.system", surfaceId: "audit.admin.system",
label: "Audit", label: "i18n:govoplan-audit.system_audit.f1a20815",
group: "SYSTEM", group: "SYSTEM",
order: 90, order: 90,
allOf: ["system:audit:read"], allOf: ["system:audit:read"],
@@ -25,7 +30,7 @@ const auditAdminSections: AdminSectionsUiCapability = {
moduleId: "audit", moduleId: "audit",
kind: "management", kind: "management",
surfaceId: "audit.admin.tenant", surfaceId: "audit.admin.tenant",
label: "Audit", label: "i18n:govoplan-audit.tenant_audit.f1a20817",
group: "TENANT", group: "TENANT",
order: 100, order: 100,
allOf: ["audit:read"], allOf: ["audit:read"],
@@ -41,12 +46,13 @@ const auditAdminSections: AdminSectionsUiCapability = {
export const auditModule: PlatformWebModule = { export const auditModule: PlatformWebModule = {
id: "audit", id: "audit",
label: "Audit", label: "Audit",
version: "0.1.6", version: "0.1.8",
dependencies: ["access", "admin"], dependencies: ["access", "admin"],
viewSurfaces: [ viewSurfaces: [
{ id: "audit.admin.system", moduleId: "audit", kind: "section", label: "System audit", order: 90 }, { id: "audit.admin.system", moduleId: "audit", kind: "section", label: "System audit", order: 90 },
{ id: "audit.admin.tenant", moduleId: "audit", kind: "section", label: "Tenant audit", order: 100 } { id: "audit.admin.tenant", moduleId: "audit", kind: "section", label: "Tenant audit", order: 100 }
], ],
translations,
uiCapabilities: { uiCapabilities: {
"admin.sections": auditAdminSections "admin.sections": auditAdminSections
} }