Migrate Audit evidence interfaces
This commit is contained in:
@@ -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).
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.");
|
||||||
@@ -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));
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user