@cosmicdrift/kumiko-bundled-features 0.383.0 → 0.384.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/event-classification.d.ts +8 -0
- package/dist/audit/event-classification.js +69 -0
- package/dist/audit/feature.js +3 -3
- package/dist/audit/handlers/audit-events.d.ts +25 -3
- package/dist/audit/handlers/audit-events.js +41 -7
- package/dist/audit/handlers/details-all-tenants.query.d.ts +3 -0
- package/dist/audit/handlers/details-all-tenants.query.js +2 -5
- package/dist/audit/handlers/details.query.d.ts +3 -0
- package/dist/audit/handlers/details.query.js +5 -3
- package/dist/audit/handlers/list-all-tenants.query.d.ts +9 -2
- package/dist/audit/handlers/list-all-tenants.query.js +10 -8
- package/dist/audit/handlers/list.query.d.ts +8 -0
- package/dist/audit/handlers/list.query.js +12 -4
- package/dist/audit/screens.d.ts +37 -0
- package/dist/audit/screens.js +26 -4
- package/dist/audit/web/audit-event-cell.d.ts +4 -0
- package/dist/audit/web/audit-event-cell.js +20 -0
- package/dist/audit/web/audit-event-labels.d.ts +7 -0
- package/dist/audit/web/audit-event-labels.js +23 -0
- package/dist/audit/web/client-plugin.js +6 -1
- package/dist/audit/web/constants.d.ts +8 -0
- package/dist/audit/web/constants.js +7 -0
- package/dist/audit/web/i18n.js +48 -1
- package/dist/auth-email-password/feature.js +3 -2
- package/dist/auth-email-password/handlers/login-options.query.d.ts +5 -0
- package/dist/auth-email-password/handlers/login-options.query.js +17 -0
- package/dist/auth-email-password/index.d.ts +1 -1
- package/dist/auth-email-password/index.js +1 -1
- package/dist/auth-email-password/{auth-paths.d.ts → web/auth-paths.d.ts} +5 -3
- package/dist/auth-email-password/{auth-paths.js → web/auth-paths.js} +5 -3
- package/dist/auth-email-password/web/client-plugin.d.ts +5 -0
- package/dist/auth-email-password/web/client-plugin.js +2 -0
- package/dist/auth-email-password/web/constants.d.ts +1 -0
- package/dist/auth-email-password/web/constants.js +1 -0
- package/dist/auth-email-password/web/default-auth-pages-gate.d.ts +4 -0
- package/dist/auth-email-password/web/default-auth-pages-gate.js +28 -0
- package/dist/auth-email-password/web/login-screen.d.ts +13 -18
- package/dist/auth-email-password/web/login-screen.js +31 -19
- package/dist/auth-mfa/schema/user-mfa.d.ts +6 -126
- package/dist/billing-foundation/entities.d.ts +8 -168
- package/dist/billing-foundation/projection.d.ts +8 -168
- package/dist/cap-counter/entity.d.ts +1 -21
- package/dist/compliance-profiles/schema/profile-selection.d.ts +2 -28
- package/dist/config/table.d.ts +6 -126
- package/dist/custom-fields/entity.d.ts +4 -84
- package/dist/data-retention/schema/tenant-retention-override.d.ts +6 -112
- package/dist/delivery/tables.d.ts +6 -126
- package/dist/document-ingest-foundation/entity.d.ts +6 -112
- package/dist/folders/entity.d.ts +8 -168
- package/dist/form-draft/entity.d.ts +2 -42
- package/dist/inbound-mail-foundation/entities.d.ts +25 -525
- package/dist/inbound-mail-foundation/projection.d.ts +20 -420
- package/dist/jobs/feature.js +22 -117
- package/dist/jobs/handlers/consumer.write.d.ts +37 -0
- package/dist/jobs/handlers/consumer.write.js +59 -0
- package/dist/jobs/handlers/consumers.query.d.ts +52 -0
- package/dist/jobs/handlers/consumers.query.js +114 -0
- package/dist/jobs/job-run-table.d.ts +6 -126
- package/dist/jobs/screens.d.ts +6 -0
- package/dist/jobs/screens.js +241 -0
- package/dist/jobs/web/constants.d.ts +20 -0
- package/dist/jobs/web/constants.js +21 -0
- package/dist/jobs/web/i18n.js +41 -0
- package/dist/ledger/entity.d.ts +13 -273
- package/dist/managed-pages/table.d.ts +12 -252
- package/dist/notes-history/entity.d.ts +14 -280
- package/dist/personal-access-tokens/schema/api-token.d.ts +6 -126
- package/dist/secrets/i18n.d.ts +0 -3
- package/dist/secrets/i18n.js +0 -3
- package/dist/secrets/table.d.ts +1 -21
- package/dist/sessions/constants.d.ts +2 -1
- package/dist/sessions/constants.js +2 -1
- package/dist/sessions/handlers/detail.query.d.ts +5 -0
- package/dist/sessions/handlers/detail.query.js +7 -3
- package/dist/sessions/handlers/list.query.d.ts +5 -0
- package/dist/sessions/handlers/list.query.js +16 -10
- package/dist/sessions/handlers/mine.query.d.ts +5 -0
- package/dist/sessions/handlers/mine.query.js +15 -9
- package/dist/sessions/schema/user-session.d.ts +4 -84
- package/dist/sessions/screens.js +22 -8
- package/dist/sessions/web/client-plugin.d.ts +6 -0
- package/dist/sessions/web/client-plugin.js +11 -0
- package/dist/sessions/web/constants.d.ts +3 -0
- package/dist/sessions/web/constants.js +3 -0
- package/dist/sessions/web/default-translations.d.ts +2 -0
- package/dist/sessions/web/default-translations.js +3 -0
- package/dist/sessions/web/i18n.js +5 -1
- package/dist/sessions/web/index.d.ts +1 -0
- package/dist/sessions/web/index.js +1 -0
- package/dist/sessions/web/session-device-cell.d.ts +3 -0
- package/dist/sessions/web/session-device-cell.js +16 -0
- package/dist/sessions/web/user-agent.d.ts +26 -0
- package/dist/sessions/web/user-agent.js +58 -0
- package/dist/tags/entity.d.ts +9 -189
- package/dist/template-resolver/feature.d.ts +1 -0
- package/dist/template-resolver/feature.js +2 -0
- package/dist/template-resolver/handlers/overview.query.d.ts +5 -0
- package/dist/template-resolver/handlers/overview.query.js +19 -2
- package/dist/template-resolver/handlers/tenant-override-counts.query.d.ts +17 -0
- package/dist/template-resolver/handlers/tenant-override-counts.query.js +35 -0
- package/dist/template-resolver/handlers/tenant-override-selection.d.ts +12 -0
- package/dist/template-resolver/handlers/tenant-override-selection.js +34 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +3 -29
- package/dist/template-resolver/table.d.ts +20 -350
- package/dist/template-resolver/user-content-table.d.ts +12 -238
- package/dist/template-resolver/web/qualified-names.d.ts +1 -0
- package/dist/template-resolver/web/qualified-names.js +1 -0
- package/dist/tenant/invitation-table.d.ts +6 -126
- package/dist/tenant/membership-table.d.ts +4 -84
- package/dist/tenant/schema/tenant.d.ts +6 -126
- package/dist/tier-engine/entity.d.ts +1 -21
- package/dist/tier-engine/tier-resolver.d.ts +1 -21
- package/dist/user/schema/user.d.ts +12 -252
- package/dist/user-data-rights/feature.js +33 -33
- package/dist/user-data-rights/schema/download-attempt.d.ts +14 -294
- package/dist/user-data-rights/schema/download-token.d.ts +8 -168
- package/dist/user-data-rights/schema/export-job.d.ts +8 -154
- package/dist/user-data-rights/web/i18n.js +4 -1
- package/dist/waitlist/entity.d.ts +16 -322
- package/package.json +17 -9
- package/src/audit/changes.json +6 -0
- package/src/auth-email-password/changes.json +6 -0
- package/src/jobs/changes.json +6 -0
- package/src/sessions/changes.json +6 -0
- package/src/template-resolver/changes.json +6 -0
- package/src/user-data-rights/changes.json +18 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const TECHNICAL_EVENT: "technical";
|
|
2
|
+
export type AuditEventStance = typeof TECHNICAL_EVENT | {
|
|
3
|
+
readonly labelKey: string;
|
|
4
|
+
};
|
|
5
|
+
export declare const AUDIT_EVENT_STANCES: Readonly<Record<string, AuditEventStance>>;
|
|
6
|
+
export declare const FRAMEWORK_SYSTEM_EVENT_TYPES: readonly string[];
|
|
7
|
+
export declare const TECHNICAL_EVENT_TYPES: readonly string[];
|
|
8
|
+
export declare function eventLabelKeyOf(type: string): string | null;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Events that are not entity lifecycle events (`<entity>.created` etc.) need an explicit
|
|
2
|
+
// audit stance: a label key for the log, or "technical" to hide them behind the facet.
|
|
3
|
+
// The boot-registry test fails for a bundled event type that has neither.
|
|
4
|
+
import { WORKFLOW_SYSTEM_EVENT_DEFS } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
|
+
import { ESCAPE_HATCH_USED_EVENT } from "./escape-hatch-audit-sink.js";
|
|
6
|
+
export const TECHNICAL_EVENT = "technical";
|
|
7
|
+
const label = (name) => ({ labelKey: `audit.log.event.${name}` });
|
|
8
|
+
export const AUDIT_EVENT_STANCES = {
|
|
9
|
+
[ESCAPE_HATCH_USED_EVENT]: TECHNICAL_EVENT,
|
|
10
|
+
"delivery:event:attempt": TECHNICAL_EVENT,
|
|
11
|
+
"delivery:event:attempt-address-erased": TECHNICAL_EVENT,
|
|
12
|
+
"cap-counter:event:rolling-incremented": TECHNICAL_EVENT,
|
|
13
|
+
"cap-counter:event:rolling-released": TECHNICAL_EVENT,
|
|
14
|
+
"inbound-mail-foundation:event:inbound-message-received": TECHNICAL_EVENT,
|
|
15
|
+
"inbound-mail-foundation:event:mail-thread-updated": TECHNICAL_EVENT,
|
|
16
|
+
"document-ingest-foundation:event:document-ingest-requested": TECHNICAL_EVENT,
|
|
17
|
+
"document-ingest-foundation:event:document-ingest-skipped": TECHNICAL_EVENT,
|
|
18
|
+
"tenant-lifecycle:event:tenant-destruction-stage-started": TECHNICAL_EVENT,
|
|
19
|
+
"tenant-lifecycle:event:tenant-destruction-stage-succeeded": TECHNICAL_EVENT,
|
|
20
|
+
"tenant-lifecycle:event:tenant-destruction-stage-progressed": TECHNICAL_EVENT,
|
|
21
|
+
"tenant-lifecycle:event:tenant-destruction-stage-failed": TECHNICAL_EVENT,
|
|
22
|
+
"tenant-lifecycle:event:tenant-destruction-stage-abandoned": TECHNICAL_EVENT,
|
|
23
|
+
"custom-fields:event:custom-field-set": label("custom-field-set"),
|
|
24
|
+
"custom-fields:event:custom-field-cleared": label("custom-field-cleared"),
|
|
25
|
+
"custom-fields:event:field-definition-deleted": label("field-definition-deleted"),
|
|
26
|
+
"secrets:event:read": label("secret-read"),
|
|
27
|
+
"sessions:event:session-revoked": label("session-revoked"),
|
|
28
|
+
"crypto-shredding:event:subject-forgotten": label("subject-forgotten"),
|
|
29
|
+
"crypto-shredding:event:forget-denied": label("forget-denied"),
|
|
30
|
+
"personal-access-tokens:event:pat-revoked": label("pat-revoked"),
|
|
31
|
+
"inbound-mail-foundation:event:mail-account-connected": label("mail-account-connected"),
|
|
32
|
+
"inbound-mail-foundation:event:mail-account-updated": label("mail-account-updated"),
|
|
33
|
+
"inbound-mail-foundation:event:mail-account-disconnected": label("mail-account-disconnected"),
|
|
34
|
+
"billing-foundation:event:subscription-created": label("subscription-created"),
|
|
35
|
+
"billing-foundation:event:subscription-updated": label("subscription-updated"),
|
|
36
|
+
"billing-foundation:event:subscription-canceled": label("subscription-canceled"),
|
|
37
|
+
"billing-foundation:event:invoice-paid": label("invoice-paid"),
|
|
38
|
+
"billing-foundation:event:invoice-payment-failed": label("invoice-payment-failed"),
|
|
39
|
+
"billing-foundation:event:payment-received": label("payment-received"),
|
|
40
|
+
"billing-foundation:event:checkout-consent-recorded": label("checkout-consent-recorded"),
|
|
41
|
+
"billing-foundation:event:contract-confirmation-issued": label("contract-confirmation-issued"),
|
|
42
|
+
"billing-foundation:event:contract-termination-requested": label("contract-termination-requested"),
|
|
43
|
+
"billing-foundation:event:contract-termination-declared": label("contract-termination-declared"),
|
|
44
|
+
"billing-foundation:event:contract-termination-unmatched": label("contract-termination-unmatched"),
|
|
45
|
+
"billing-foundation:event:contract-termination-revoked": label("contract-termination-revoked"),
|
|
46
|
+
"tenant-lifecycle:event:destruction-requested": label("destruction-requested"),
|
|
47
|
+
"tenant-lifecycle:event:destruction-cancelled": label("destruction-cancelled"),
|
|
48
|
+
"tenant-lifecycle:event:tenant-destruction-started": label("tenant-destruction-started"),
|
|
49
|
+
"tenant-lifecycle:event:tenant-destruction-completed": label("tenant-destruction-completed"),
|
|
50
|
+
"tenant-lifecycle:event:tenant-destruction-failed": label("tenant-destruction-failed"),
|
|
51
|
+
"tenant-handover:event:claimed": label("tenant-handover-claimed"),
|
|
52
|
+
"waitlist:event:exported": label("waitlist-exported"),
|
|
53
|
+
"waitlist:event:purged": label("waitlist-purged"),
|
|
54
|
+
"feature-toggles:event:toggle-set": label("feature-toggle-set"),
|
|
55
|
+
};
|
|
56
|
+
// Framework system events (kumiko:system:*) are engine plumbing, never audit-worthy on their own.
|
|
57
|
+
export const FRAMEWORK_SYSTEM_EVENT_TYPES = [
|
|
58
|
+
...WORKFLOW_SYSTEM_EVENT_DEFS.keys(),
|
|
59
|
+
];
|
|
60
|
+
export const TECHNICAL_EVENT_TYPES = [
|
|
61
|
+
...Object.entries(AUDIT_EVENT_STANCES)
|
|
62
|
+
.filter(([, stance]) => stance === TECHNICAL_EVENT)
|
|
63
|
+
.map(([type]) => type),
|
|
64
|
+
...FRAMEWORK_SYSTEM_EVENT_TYPES,
|
|
65
|
+
];
|
|
66
|
+
export function eventLabelKeyOf(type) {
|
|
67
|
+
const stance = AUDIT_EVENT_STANCES[type];
|
|
68
|
+
return stance === undefined || stance === TECHNICAL_EVENT ? null : stance.labelKey;
|
|
69
|
+
}
|
package/dist/audit/feature.js
CHANGED
|
@@ -5,7 +5,7 @@ import { detailsAllTenantsQuery } from "./handlers/details-all-tenants.query.js"
|
|
|
5
5
|
import { listQuery } from "./handlers/list.query.js";
|
|
6
6
|
import { listAllTenantsQuery } from "./handlers/list-all-tenants.query.js";
|
|
7
7
|
import { runEscapeHatchRetention } from "./run-escape-hatch-retention.js";
|
|
8
|
-
import { AUDIT_LOG_ALL_TENANTS_COLUMNS, AUDIT_LOG_COLUMNS, AUDIT_LOG_DATE_RANGE_FACET, AUDIT_LOG_DETAIL_FIELD_LABELS, AUDIT_LOG_TENANT_FACET, auditLogDetailLayout, } from "./screens.js";
|
|
8
|
+
import { AUDIT_LOG_ALL_TENANTS_COLUMNS, AUDIT_LOG_COLUMNS, AUDIT_LOG_DATE_RANGE_FACET, AUDIT_LOG_DETAIL_FIELD_LABELS, AUDIT_LOG_TECHNICAL_FACET, AUDIT_LOG_TENANT_FACET, auditLogDetailLayout, } from "./screens.js";
|
|
9
9
|
import { AUDIT_LOG_ALL_TENANTS_DETAIL_SCREEN_ID, AUDIT_LOG_ALL_TENANTS_NAV_ID, AUDIT_LOG_ALL_TENANTS_SCREEN_ID, AUDIT_LOG_DETAIL_SCREEN_ID, AUDIT_LOG_SCREEN_ID, AuditQueries, DEFAULT_ESCAPE_HATCH_RETENTION_DAYS, } from "./web/constants.js";
|
|
10
10
|
import { AUDIT_I18N } from "./web/i18n.js";
|
|
11
11
|
// Audit feature — exposes a filtered read over the framework's event log.
|
|
@@ -74,7 +74,7 @@ export function createAuditFeature() {
|
|
|
74
74
|
columns: AUDIT_LOG_COLUMNS,
|
|
75
75
|
searchable: true,
|
|
76
76
|
defaultSort: { field: "createdAt", dir: "desc" },
|
|
77
|
-
facets: [AUDIT_LOG_DATE_RANGE_FACET],
|
|
77
|
+
facets: [AUDIT_LOG_DATE_RANGE_FACET, AUDIT_LOG_TECHNICAL_FACET],
|
|
78
78
|
rowActions: [
|
|
79
79
|
{
|
|
80
80
|
kind: "navigate",
|
|
@@ -106,7 +106,7 @@ export function createAuditFeature() {
|
|
|
106
106
|
query: AuditQueries.listAllTenants,
|
|
107
107
|
columns: AUDIT_LOG_ALL_TENANTS_COLUMNS,
|
|
108
108
|
defaultSort: { field: "createdAt", dir: "desc" },
|
|
109
|
-
facets: [AUDIT_LOG_DATE_RANGE_FACET, AUDIT_LOG_TENANT_FACET],
|
|
109
|
+
facets: [AUDIT_LOG_DATE_RANGE_FACET, AUDIT_LOG_TENANT_FACET, AUDIT_LOG_TECHNICAL_FACET],
|
|
110
110
|
rowActions: [
|
|
111
111
|
{
|
|
112
112
|
kind: "navigate",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type TenantDb, type WhereObject } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
3
|
import * as z from "zod";
|
|
3
4
|
export declare const auditListPayloadShape: {
|
|
4
5
|
readonly cursor: z.ZodOptional<z.ZodString>;
|
|
@@ -20,6 +21,15 @@ export declare const auditListPayloadShape: {
|
|
|
20
21
|
readonly from: z.ZodOptional<z.ZodISODateTime>;
|
|
21
22
|
readonly to: z.ZodOptional<z.ZodISODateTime>;
|
|
22
23
|
};
|
|
24
|
+
export declare const technicalEntriesFilterSchema: z.ZodObject<{
|
|
25
|
+
field: z.ZodLiteral<"type">;
|
|
26
|
+
op: z.ZodLiteral<"in">;
|
|
27
|
+
value: z.ZodArray<z.ZodLiteral<"include-technical">>;
|
|
28
|
+
}, z.core.$strict>;
|
|
29
|
+
export declare function includesTechnicalEntries(filters: readonly {
|
|
30
|
+
readonly field: string;
|
|
31
|
+
readonly value: readonly unknown[];
|
|
32
|
+
}[] | undefined): boolean;
|
|
23
33
|
export declare const auditEventIdSchema: z.ZodString;
|
|
24
34
|
export declare const AUDIT_DATE_RANGE_CHECK: {
|
|
25
35
|
isValid: (v: {
|
|
@@ -41,6 +51,7 @@ export type AuditListFilters = {
|
|
|
41
51
|
to?: string | undefined;
|
|
42
52
|
cursor?: string | undefined;
|
|
43
53
|
before?: string | undefined;
|
|
54
|
+
includeTechnical?: boolean | undefined;
|
|
44
55
|
};
|
|
45
56
|
/** `tenantCondition` is a tenant id, or an operator object for reads across tenants. */
|
|
46
57
|
export declare function buildAuditWhere(tenantCondition: unknown, p: AuditListFilters, direction: "asc" | "desc"): WhereObject;
|
|
@@ -56,25 +67,33 @@ type AuditEventRow = {
|
|
|
56
67
|
createdAt: unknown;
|
|
57
68
|
createdBy: string;
|
|
58
69
|
};
|
|
59
|
-
export
|
|
70
|
+
export type EntityNounKeyResolver = (aggregateType: string) => string | null;
|
|
71
|
+
export declare function createEntityNounKeyResolver(registry: Registry | undefined): EntityNounKeyResolver;
|
|
72
|
+
export declare function serializeAuditEvent(r: AuditEventRow, nounKeyOf: EntityNounKeyResolver): {
|
|
60
73
|
id: string;
|
|
61
74
|
tenantId: string;
|
|
62
75
|
aggregateId: string;
|
|
63
76
|
aggregateType: string;
|
|
64
77
|
version: number;
|
|
65
78
|
type: string;
|
|
79
|
+
eventAction: "created" | "deleted" | "restored" | "updated" | null;
|
|
80
|
+
eventLabelKey: string | null;
|
|
81
|
+
entityNounKey: string | null;
|
|
66
82
|
payload: unknown;
|
|
67
83
|
metadata: unknown;
|
|
68
84
|
createdAt: unknown;
|
|
69
85
|
createdBy: string;
|
|
70
86
|
};
|
|
71
|
-
export declare function loadAuditEvent(db: TenantDb, where: WhereObject): Promise<{
|
|
87
|
+
export declare function loadAuditEvent(db: TenantDb, where: WhereObject, nounKeyOf: EntityNounKeyResolver): Promise<{
|
|
72
88
|
id: string;
|
|
73
89
|
tenantId: string;
|
|
74
90
|
aggregateId: string;
|
|
75
91
|
aggregateType: string;
|
|
76
92
|
version: number;
|
|
77
93
|
type: string;
|
|
94
|
+
eventAction: "created" | "deleted" | "restored" | "updated" | null;
|
|
95
|
+
eventLabelKey: string | null;
|
|
96
|
+
entityNounKey: string | null;
|
|
78
97
|
payload: unknown;
|
|
79
98
|
metadata: unknown;
|
|
80
99
|
createdAt: unknown;
|
|
@@ -83,7 +102,7 @@ export declare function loadAuditEvent(db: TenantDb, where: WhereObject): Promis
|
|
|
83
102
|
export declare function listAuditEvents(db: TenantDb, where: WhereObject, p: {
|
|
84
103
|
limit: number;
|
|
85
104
|
sort?: "createdAt" | "type" | undefined;
|
|
86
|
-
}, direction: "asc" | "desc"): Promise<{
|
|
105
|
+
}, direction: "asc" | "desc", nounKeyOf: EntityNounKeyResolver): Promise<{
|
|
87
106
|
rows: {
|
|
88
107
|
id: string;
|
|
89
108
|
tenantId: string;
|
|
@@ -91,6 +110,9 @@ export declare function listAuditEvents(db: TenantDb, where: WhereObject, p: {
|
|
|
91
110
|
aggregateType: string;
|
|
92
111
|
version: number;
|
|
93
112
|
type: string;
|
|
113
|
+
eventAction: "created" | "deleted" | "restored" | "updated" | null;
|
|
114
|
+
eventLabelKey: string | null;
|
|
115
|
+
entityNounKey: string | null;
|
|
94
116
|
payload: unknown;
|
|
95
117
|
metadata: unknown;
|
|
96
118
|
createdAt: unknown;
|
|
@@ -9,6 +9,8 @@ import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
|
9
9
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
10
10
|
import * as z from "zod";
|
|
11
11
|
import { redactPiiCiphertext } from "../../shared/index.js";
|
|
12
|
+
import { eventLabelKeyOf, TECHNICAL_EVENT_TYPES } from "../event-classification.js";
|
|
13
|
+
import { AUDIT_EVENT_ACTIONS, INCLUDE_TECHNICAL_FILTER, } from "../web/constants.js";
|
|
12
14
|
const MAX_LIMIT = 100;
|
|
13
15
|
// At most 18 digits keeps the id inside bigint, so an oversized cursor or id is a 422, not a 500.
|
|
14
16
|
const CURSOR_PATTERN = /^\d{1,18}$/;
|
|
@@ -26,6 +28,15 @@ export const auditListPayloadShape = {
|
|
|
26
28
|
from: z.iso.datetime().optional(),
|
|
27
29
|
to: z.iso.datetime().optional(),
|
|
28
30
|
};
|
|
31
|
+
// The technical-entries facet rides on the `type` column (facets need a declared column).
|
|
32
|
+
export const technicalEntriesFilterSchema = z.strictObject({
|
|
33
|
+
field: z.literal("type"),
|
|
34
|
+
op: z.literal("in"),
|
|
35
|
+
value: z.array(z.literal(INCLUDE_TECHNICAL_FILTER)).max(1),
|
|
36
|
+
});
|
|
37
|
+
export function includesTechnicalEntries(filters) {
|
|
38
|
+
return (filters?.some((f) => f.field === "type" && f.value.includes(INCLUDE_TECHNICAL_FILTER)) ?? false);
|
|
39
|
+
}
|
|
29
40
|
export const auditEventIdSchema = z
|
|
30
41
|
.string()
|
|
31
42
|
.regex(/^[1-9]\d{0,17}$/, "id must be a positive integer");
|
|
@@ -58,8 +69,12 @@ export function buildAuditWhere(tenantCondition, p, direction) {
|
|
|
58
69
|
where["aggregateId"] = p.aggregateId;
|
|
59
70
|
if (p.eventType)
|
|
60
71
|
where["type"] = p.eventType;
|
|
61
|
-
else if (p.search)
|
|
62
|
-
where["type"] = {
|
|
72
|
+
else if (p.search || !p.includeTechnical) {
|
|
73
|
+
where["type"] = {
|
|
74
|
+
...(p.search && { like: `%${escapeLikePattern(p.search)}%` }),
|
|
75
|
+
...(!p.includeTechnical && { notIn: TECHNICAL_EVENT_TYPES }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
63
78
|
if (p.userId)
|
|
64
79
|
where["createdBy"] = p.userId;
|
|
65
80
|
const range = buildDateRange(p.from, p.to);
|
|
@@ -72,7 +87,23 @@ export function buildAuditWhere(tenantCondition, p, direction) {
|
|
|
72
87
|
where["id"] = direction === "asc" ? { gt: BigInt(cursor) } : { lt: BigInt(cursor) };
|
|
73
88
|
return where;
|
|
74
89
|
}
|
|
75
|
-
|
|
90
|
+
function eventActionOf(type) {
|
|
91
|
+
const suffix = type.slice(type.lastIndexOf(".") + 1);
|
|
92
|
+
return type.includes(".") ? (AUDIT_EVENT_ACTIONS.find((a) => a === suffix) ?? null) : null;
|
|
93
|
+
}
|
|
94
|
+
export function createEntityNounKeyResolver(registry) {
|
|
95
|
+
const featureByEntity = new Map();
|
|
96
|
+
for (const [featureName, feature] of registry?.features ?? []) {
|
|
97
|
+
for (const entityName of Object.keys(feature.entities ?? {})) {
|
|
98
|
+
featureByEntity.set(entityName, featureName);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return (aggregateType) => {
|
|
102
|
+
const featureName = featureByEntity.get(aggregateType);
|
|
103
|
+
return featureName === undefined ? null : `${featureName}:entity:${aggregateType}:noun`;
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function serializeAuditEvent(r, nounKeyOf) {
|
|
76
107
|
return {
|
|
77
108
|
id: String(r.id),
|
|
78
109
|
tenantId: r.tenantId,
|
|
@@ -80,18 +111,21 @@ export function serializeAuditEvent(r) {
|
|
|
80
111
|
aggregateType: r.aggregateType,
|
|
81
112
|
version: r.version,
|
|
82
113
|
type: r.type,
|
|
114
|
+
eventAction: eventActionOf(r.type),
|
|
115
|
+
eventLabelKey: eventLabelKeyOf(r.type),
|
|
116
|
+
entityNounKey: nounKeyOf(r.aggregateType),
|
|
83
117
|
payload: redactPiiCiphertext(r.payload),
|
|
84
118
|
metadata: redactPiiCiphertext(r.metadata),
|
|
85
119
|
createdAt: r.createdAt,
|
|
86
120
|
createdBy: r.createdBy,
|
|
87
121
|
};
|
|
88
122
|
}
|
|
89
|
-
export async function loadAuditEvent(db, where) {
|
|
123
|
+
export async function loadAuditEvent(db, where, nounKeyOf) {
|
|
90
124
|
const rows = await selectMany(db, eventsTable, where, { limit: 1 });
|
|
91
125
|
const row = rows[0];
|
|
92
|
-
return row === undefined ? null : serializeAuditEvent(row);
|
|
126
|
+
return row === undefined ? null : serializeAuditEvent(row, nounKeyOf);
|
|
93
127
|
}
|
|
94
|
-
export async function listAuditEvents(db, where, p, direction) {
|
|
128
|
+
export async function listAuditEvents(db, where, p, direction, nounKeyOf) {
|
|
95
129
|
const rows = await selectMany(db, eventsTable, where, {
|
|
96
130
|
// The id cursor only resumes an id-ordered listing. created_at comes from the
|
|
97
131
|
// transaction start and can run against insert order, so ordering by it would let
|
|
@@ -104,7 +138,7 @@ export async function listAuditEvents(db, where, p, direction) {
|
|
|
104
138
|
: [{ col: "id", direction }],
|
|
105
139
|
limit: p.limit,
|
|
106
140
|
});
|
|
107
|
-
const serialised = rows.map(serializeAuditEvent);
|
|
141
|
+
const serialised = rows.map((row) => serializeAuditEvent(row, nounKeyOf));
|
|
108
142
|
const last = serialised[serialised.length - 1];
|
|
109
143
|
// An id cursor cannot resume a type-ordered listing, so that sort is one page.
|
|
110
144
|
const nextId = serialised.length === p.limit && last && p.sort !== "type" ? last.id : null;
|
|
@@ -8,6 +8,9 @@ export declare const detailsAllTenantsQuery: import("@cosmicdrift/kumiko-types/d
|
|
|
8
8
|
aggregateType: string;
|
|
9
9
|
version: number;
|
|
10
10
|
type: string;
|
|
11
|
+
eventAction: "created" | "deleted" | "restored" | "updated" | null;
|
|
12
|
+
eventLabelKey: string | null;
|
|
13
|
+
entityNounKey: string | null;
|
|
11
14
|
payload: unknown;
|
|
12
15
|
metadata: unknown;
|
|
13
16
|
createdAt: unknown;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// One audit event of any tenant by its event-store id, for the all-tenants log's detail screen.
|
|
2
2
|
import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
3
|
import * as z from "zod";
|
|
4
|
-
import { auditEventIdSchema, loadAuditEvent } from "./audit-events.js";
|
|
4
|
+
import { auditEventIdSchema, createEntityNounKeyResolver, loadAuditEvent } from "./audit-events.js";
|
|
5
5
|
import { requireAuditSystemDb } from "./audit-system-db.js";
|
|
6
6
|
import { allTenantsCondition, assertMayReadAllTenants } from "./resolve-audit-tenant.js";
|
|
7
7
|
export const detailsAllTenantsQuery = defineQueryHandler({
|
|
@@ -12,9 +12,6 @@ export const detailsAllTenantsQuery = defineQueryHandler({
|
|
|
12
12
|
handler: async (query, ctx) => {
|
|
13
13
|
assertMayReadAllTenants(query.user);
|
|
14
14
|
const db = requireAuditSystemDb(ctx.systemDb, "audit:query:details-all-tenants").acknowledgeCrossTenant("SystemAdmin reads one audit event of any tenant");
|
|
15
|
-
return loadAuditEvent(db, {
|
|
16
|
-
tenantId: allTenantsCondition(undefined),
|
|
17
|
-
id: BigInt(query.payload.id),
|
|
18
|
-
});
|
|
15
|
+
return loadAuditEvent(db, { tenantId: allTenantsCondition(undefined), id: BigInt(query.payload.id) }, createEntityNounKeyResolver(ctx.registry));
|
|
19
16
|
},
|
|
20
17
|
});
|
|
@@ -12,6 +12,9 @@ export declare const detailsQuery: import("@cosmicdrift/kumiko-types/define-hand
|
|
|
12
12
|
aggregateType: string;
|
|
13
13
|
version: number;
|
|
14
14
|
type: string;
|
|
15
|
+
eventAction: "created" | "deleted" | "restored" | "updated" | null;
|
|
16
|
+
eventLabelKey: string | null;
|
|
17
|
+
entityNounKey: string | null;
|
|
15
18
|
payload: unknown;
|
|
16
19
|
metadata: unknown;
|
|
17
20
|
createdAt: unknown;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
6
6
|
import * as z from "zod";
|
|
7
7
|
import { AUDIT_SCOPE_VALUES } from "../web/constants.js";
|
|
8
|
-
import { auditEventIdSchema, loadAuditEvent } from "./audit-events.js";
|
|
8
|
+
import { auditEventIdSchema, createEntityNounKeyResolver, loadAuditEvent } from "./audit-events.js";
|
|
9
9
|
import { requireAuditSystemDb } from "./audit-system-db.js";
|
|
10
10
|
import { resolveAuditScopeFilter } from "./resolve-audit-tenant.js";
|
|
11
11
|
export const detailsQuery = defineQueryHandler({
|
|
@@ -21,8 +21,10 @@ export const detailsQuery = defineQueryHandler({
|
|
|
21
21
|
const db = requireAuditSystemDb(ctx.systemDb, "audit:query:details").assertTenantMatch(query.user.tenantId);
|
|
22
22
|
return loadAuditEvent(db, {
|
|
23
23
|
tenantId: scopeFilter.tenantId,
|
|
24
|
-
...(scopeFilter.aggregateType !== undefined && {
|
|
24
|
+
...(scopeFilter.aggregateType !== undefined && {
|
|
25
|
+
aggregateType: scopeFilter.aggregateType,
|
|
26
|
+
}),
|
|
25
27
|
id: BigInt(query.payload.id),
|
|
26
|
-
});
|
|
28
|
+
}, createEntityNounKeyResolver(ctx.registry));
|
|
27
29
|
},
|
|
28
30
|
});
|
|
@@ -18,11 +18,15 @@ export declare const listAllTenantsQuery: import("@cosmicdrift/kumiko-types/defi
|
|
|
18
18
|
}>>;
|
|
19
19
|
from: z.ZodOptional<z.ZodISODateTime>;
|
|
20
20
|
to: z.ZodOptional<z.ZodISODateTime>;
|
|
21
|
-
filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
21
|
+
filters: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
22
22
|
field: z.ZodLiteral<"tenantId">;
|
|
23
23
|
op: z.ZodLiteral<"in">;
|
|
24
24
|
value: z.ZodArray<z.ZodUUID>;
|
|
25
|
-
}, z.core.$strict
|
|
25
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
26
|
+
field: z.ZodLiteral<"type">;
|
|
27
|
+
op: z.ZodLiteral<"in">;
|
|
28
|
+
value: z.ZodArray<z.ZodLiteral<"include-technical">>;
|
|
29
|
+
}, z.core.$strict>]>>>;
|
|
26
30
|
}, z.core.$strip>, {
|
|
27
31
|
rows: {
|
|
28
32
|
id: string;
|
|
@@ -31,6 +35,9 @@ export declare const listAllTenantsQuery: import("@cosmicdrift/kumiko-types/defi
|
|
|
31
35
|
aggregateType: string;
|
|
32
36
|
version: number;
|
|
33
37
|
type: string;
|
|
38
|
+
eventAction: "created" | "deleted" | "restored" | "updated" | null;
|
|
39
|
+
eventLabelKey: string | null;
|
|
40
|
+
entityNounKey: string | null;
|
|
34
41
|
payload: unknown;
|
|
35
42
|
metadata: unknown;
|
|
36
43
|
createdAt: unknown;
|
|
@@ -2,31 +2,33 @@
|
|
|
2
2
|
// allTenantsCondition). Rows carry their tenantId so the screen can label the tenant.
|
|
3
3
|
import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import * as z from "zod";
|
|
5
|
-
import { AUDIT_DATE_RANGE_CHECK, auditListPayloadShape, buildAuditWhere, listAuditEvents, } from "./audit-events.js";
|
|
5
|
+
import { AUDIT_DATE_RANGE_CHECK, auditListPayloadShape, buildAuditWhere, createEntityNounKeyResolver, includesTechnicalEntries, listAuditEvents, technicalEntriesFilterSchema, } from "./audit-events.js";
|
|
6
6
|
import { requireAuditSystemDb } from "./audit-system-db.js";
|
|
7
7
|
import { allTenantsCondition, assertMayReadAllTenants } from "./resolve-audit-tenant.js";
|
|
8
8
|
const MAX_TENANT_FILTER_IDS = 100;
|
|
9
9
|
// The reference facet on the tenant column sends exactly this shape.
|
|
10
|
-
const
|
|
11
|
-
.array(z.strictObject({
|
|
10
|
+
const tenantIdFilterSchema = z.strictObject({
|
|
12
11
|
field: z.literal("tenantId"),
|
|
13
12
|
op: z.literal("in"),
|
|
14
13
|
value: z.array(z.uuid()).max(MAX_TENANT_FILTER_IDS),
|
|
15
|
-
})
|
|
16
|
-
|
|
14
|
+
});
|
|
15
|
+
const auditFiltersSchema = z
|
|
16
|
+
.array(z.union([tenantIdFilterSchema, technicalEntriesFilterSchema]))
|
|
17
|
+
.max(2);
|
|
17
18
|
export const listAllTenantsQuery = defineQueryHandler({
|
|
18
19
|
name: "list-all-tenants",
|
|
19
20
|
description: "Lists audit-trail events of every tenant newest-first with cursor paging, each row carrying its tenantId; filterable by tenant, aggregate type, aggregate id, event type, actor and time range. SystemAdmin only; events of the system tenant are not included.",
|
|
20
21
|
schema: z
|
|
21
|
-
.object({ ...auditListPayloadShape, filters:
|
|
22
|
+
.object({ ...auditListPayloadShape, filters: auditFiltersSchema.optional() })
|
|
22
23
|
.refine(AUDIT_DATE_RANGE_CHECK.isValid, AUDIT_DATE_RANGE_CHECK.params),
|
|
23
24
|
access: { roles: access.systemAdmin },
|
|
24
25
|
handler: async (query, ctx) => {
|
|
25
26
|
assertMayReadAllTenants(query.user);
|
|
26
27
|
const p = query.payload;
|
|
27
28
|
const direction = p.sortDirection ?? "desc";
|
|
28
|
-
const
|
|
29
|
+
const requestedTenantIds = p.filters?.find((f) => f.field === "tenantId")?.value;
|
|
30
|
+
const where = buildAuditWhere(allTenantsCondition(requestedTenantIds), { ...p, includeTechnical: includesTechnicalEntries(p.filters) }, direction);
|
|
29
31
|
const db = requireAuditSystemDb(ctx.systemDb, "audit:query:list-all-tenants").acknowledgeCrossTenant("SystemAdmin reads the audit trail of every tenant");
|
|
30
|
-
return listAuditEvents(db, where, p, direction);
|
|
32
|
+
return listAuditEvents(db, where, p, direction, createEntityNounKeyResolver(ctx.registry));
|
|
31
33
|
},
|
|
32
34
|
});
|
|
@@ -22,6 +22,11 @@ export declare const listQuery: import("@cosmicdrift/kumiko-types/define-handler
|
|
|
22
22
|
system: "system";
|
|
23
23
|
tenant: "tenant";
|
|
24
24
|
}>>;
|
|
25
|
+
filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
26
|
+
field: z.ZodLiteral<"type">;
|
|
27
|
+
op: z.ZodLiteral<"in">;
|
|
28
|
+
value: z.ZodArray<z.ZodLiteral<"include-technical">>;
|
|
29
|
+
}, z.core.$strict>>>;
|
|
25
30
|
}, z.core.$strip>, {
|
|
26
31
|
rows: {
|
|
27
32
|
id: string;
|
|
@@ -30,6 +35,9 @@ export declare const listQuery: import("@cosmicdrift/kumiko-types/define-handler
|
|
|
30
35
|
aggregateType: string;
|
|
31
36
|
version: number;
|
|
32
37
|
type: string;
|
|
38
|
+
eventAction: "created" | "deleted" | "restored" | "updated" | null;
|
|
39
|
+
eventLabelKey: string | null;
|
|
40
|
+
entityNounKey: string | null;
|
|
33
41
|
payload: unknown;
|
|
34
42
|
metadata: unknown;
|
|
35
43
|
createdAt: unknown;
|
|
@@ -11,22 +11,30 @@
|
|
|
11
11
|
import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
12
12
|
import * as z from "zod";
|
|
13
13
|
import { AUDIT_SCOPE_VALUES } from "../web/constants.js";
|
|
14
|
-
import { AUDIT_DATE_RANGE_CHECK, auditListPayloadShape, buildAuditWhere, listAuditEvents, } from "./audit-events.js";
|
|
14
|
+
import { AUDIT_DATE_RANGE_CHECK, auditListPayloadShape, buildAuditWhere, createEntityNounKeyResolver, includesTechnicalEntries, listAuditEvents, technicalEntriesFilterSchema, } from "./audit-events.js";
|
|
15
15
|
import { requireAuditSystemDb } from "./audit-system-db.js";
|
|
16
16
|
import { resolveAuditScopeFilter } from "./resolve-audit-tenant.js";
|
|
17
17
|
export const listQuery = defineQueryHandler({
|
|
18
18
|
name: "list",
|
|
19
19
|
description: 'Lists the tenant\'s audit-trail events newest-first with cursor paging, filterable by aggregate type, aggregate id, event type, actor and time range; use it to answer who changed what and when. A SystemAdmin can set scope "system" to read the app-instance system events (e.g. app.started) instead of the own tenant.',
|
|
20
20
|
schema: z
|
|
21
|
-
.object({
|
|
21
|
+
.object({
|
|
22
|
+
...auditListPayloadShape,
|
|
23
|
+
scope: z.enum(AUDIT_SCOPE_VALUES).optional(),
|
|
24
|
+
filters: z.array(technicalEntriesFilterSchema).max(1).optional(),
|
|
25
|
+
})
|
|
22
26
|
.refine(AUDIT_DATE_RANGE_CHECK.isValid, AUDIT_DATE_RANGE_CHECK.params),
|
|
23
27
|
access: { roles: access.admin },
|
|
24
28
|
handler: async (query, ctx) => {
|
|
25
29
|
const p = query.payload;
|
|
26
30
|
const scopeFilter = resolveAuditScopeFilter(query.user, p.scope);
|
|
27
31
|
const direction = p.sortDirection ?? "desc";
|
|
28
|
-
const where = buildAuditWhere(scopeFilter.tenantId, {
|
|
32
|
+
const where = buildAuditWhere(scopeFilter.tenantId, {
|
|
33
|
+
...p,
|
|
34
|
+
aggregateType: scopeFilter.aggregateType ?? p.aggregateType,
|
|
35
|
+
includeTechnical: includesTechnicalEntries(p.filters),
|
|
36
|
+
}, direction);
|
|
29
37
|
const db = requireAuditSystemDb(ctx.systemDb, "audit:query:list").assertTenantMatch(query.user.tenantId);
|
|
30
|
-
return listAuditEvents(db, where, p, direction);
|
|
38
|
+
return listAuditEvents(db, where, p, direction, createEntityNounKeyResolver(ctx.registry));
|
|
31
39
|
},
|
|
32
40
|
});
|
package/dist/audit/screens.d.ts
CHANGED
|
@@ -8,6 +8,20 @@ export declare const AUDIT_LOG_COLUMNS: readonly [{
|
|
|
8
8
|
}, {
|
|
9
9
|
readonly field: "type";
|
|
10
10
|
readonly label: string;
|
|
11
|
+
readonly renderer: {
|
|
12
|
+
readonly react: {
|
|
13
|
+
readonly __component: "audit-event-cell";
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
}, {
|
|
17
|
+
readonly field: "aggregateType";
|
|
18
|
+
readonly label: string;
|
|
19
|
+
readonly renderer: {
|
|
20
|
+
readonly react: {
|
|
21
|
+
readonly __component: "audit-object-cell";
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
readonly hideOnNarrow: true;
|
|
11
25
|
}, {
|
|
12
26
|
readonly refEntity: "user:user";
|
|
13
27
|
readonly refLabelField: "displayName";
|
|
@@ -28,6 +42,20 @@ export declare const AUDIT_LOG_ALL_TENANTS_COLUMNS: readonly [{
|
|
|
28
42
|
}, {
|
|
29
43
|
readonly field: "type";
|
|
30
44
|
readonly label: string;
|
|
45
|
+
readonly renderer: {
|
|
46
|
+
readonly react: {
|
|
47
|
+
readonly __component: "audit-event-cell";
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}, {
|
|
51
|
+
readonly field: "aggregateType";
|
|
52
|
+
readonly label: string;
|
|
53
|
+
readonly renderer: {
|
|
54
|
+
readonly react: {
|
|
55
|
+
readonly __component: "audit-object-cell";
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
readonly hideOnNarrow: true;
|
|
31
59
|
}, {
|
|
32
60
|
readonly refEntity: "user:user";
|
|
33
61
|
readonly refLabelField: "displayName";
|
|
@@ -43,6 +71,15 @@ export declare const AUDIT_LOG_DATE_RANGE_FACET: {
|
|
|
43
71
|
readonly to: "to";
|
|
44
72
|
};
|
|
45
73
|
};
|
|
74
|
+
export declare const AUDIT_LOG_TECHNICAL_FACET: {
|
|
75
|
+
readonly type: "select";
|
|
76
|
+
readonly field: "type";
|
|
77
|
+
readonly label: string;
|
|
78
|
+
readonly options: readonly [{
|
|
79
|
+
readonly value: "include-technical";
|
|
80
|
+
readonly label: string;
|
|
81
|
+
}];
|
|
82
|
+
};
|
|
46
83
|
export declare const AUDIT_LOG_TENANT_FACET: {
|
|
47
84
|
readonly type: "reference";
|
|
48
85
|
readonly field: "tenantId";
|
package/dist/audit/screens.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Columns, labels and layout shared by the tenant audit log and its all-tenants variant, so the
|
|
2
2
|
// two screen pairs cannot drift apart. The all-tenants variant only adds the tenant.
|
|
3
3
|
import { i18nKey } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import { AUDIT_EVENT_CELL_COMPONENT, AUDIT_OBJECT_CELL_COMPONENT, INCLUDE_TECHNICAL_FILTER, } from "./web/constants.js";
|
|
4
5
|
const TENANT_REFERENCE = { refEntity: "tenant:tenant", refLabelField: "name" };
|
|
5
6
|
const ACTOR_REFERENCE = { refEntity: "user:user", refLabelField: "displayName" };
|
|
6
7
|
const WHEN_COLUMN = {
|
|
@@ -13,17 +14,30 @@ const TENANT_COLUMN = {
|
|
|
13
14
|
label: i18nKey("audit.log.col.tenant"),
|
|
14
15
|
...TENANT_REFERENCE,
|
|
15
16
|
};
|
|
16
|
-
const
|
|
17
|
+
const EVENT_RENDERER = { react: { __component: AUDIT_EVENT_CELL_COMPONENT } };
|
|
18
|
+
const OBJECT_RENDERER = { react: { __component: AUDIT_OBJECT_CELL_COMPONENT } };
|
|
19
|
+
const TYPE_COLUMN = {
|
|
20
|
+
field: "type",
|
|
21
|
+
label: i18nKey("audit.log.col.type"),
|
|
22
|
+
renderer: EVENT_RENDERER,
|
|
23
|
+
};
|
|
24
|
+
const OBJECT_COLUMN = {
|
|
25
|
+
field: "aggregateType",
|
|
26
|
+
label: i18nKey("audit.log.col.aggregateType"),
|
|
27
|
+
renderer: OBJECT_RENDERER,
|
|
28
|
+
hideOnNarrow: true,
|
|
29
|
+
};
|
|
17
30
|
const ACTOR_COLUMN = {
|
|
18
31
|
field: "createdBy",
|
|
19
32
|
label: i18nKey("audit.log.col.actor"),
|
|
20
33
|
...ACTOR_REFERENCE,
|
|
21
34
|
};
|
|
22
|
-
export const AUDIT_LOG_COLUMNS = [WHEN_COLUMN, TYPE_COLUMN, ACTOR_COLUMN];
|
|
35
|
+
export const AUDIT_LOG_COLUMNS = [WHEN_COLUMN, TYPE_COLUMN, OBJECT_COLUMN, ACTOR_COLUMN];
|
|
23
36
|
export const AUDIT_LOG_ALL_TENANTS_COLUMNS = [
|
|
24
37
|
WHEN_COLUMN,
|
|
25
38
|
TENANT_COLUMN,
|
|
26
39
|
TYPE_COLUMN,
|
|
40
|
+
OBJECT_COLUMN,
|
|
27
41
|
ACTOR_COLUMN,
|
|
28
42
|
];
|
|
29
43
|
export const AUDIT_LOG_DATE_RANGE_FACET = {
|
|
@@ -32,6 +46,14 @@ export const AUDIT_LOG_DATE_RANGE_FACET = {
|
|
|
32
46
|
label: i18nKey("audit.log.col.when"),
|
|
33
47
|
params: { from: "from", to: "to" },
|
|
34
48
|
};
|
|
49
|
+
export const AUDIT_LOG_TECHNICAL_FACET = {
|
|
50
|
+
type: "select",
|
|
51
|
+
field: "type",
|
|
52
|
+
label: i18nKey("audit.log.facet.technical"),
|
|
53
|
+
options: [
|
|
54
|
+
{ value: INCLUDE_TECHNICAL_FILTER, label: i18nKey("audit.log.facet.technical.include") },
|
|
55
|
+
],
|
|
56
|
+
};
|
|
35
57
|
export const AUDIT_LOG_TENANT_FACET = {
|
|
36
58
|
type: "reference",
|
|
37
59
|
field: "tenantId",
|
|
@@ -54,10 +76,10 @@ export function auditLogDetailLayout(options) {
|
|
|
54
76
|
sections: [
|
|
55
77
|
{
|
|
56
78
|
fields: [
|
|
57
|
-
"type",
|
|
79
|
+
{ field: "type", renderer: EVENT_RENDERER },
|
|
58
80
|
{ field: "createdAt", renderer: { format: "timestamp" } },
|
|
59
81
|
...(options.withTenant ? [{ field: "tenantId", ...TENANT_REFERENCE }] : []),
|
|
60
|
-
"aggregateType",
|
|
82
|
+
{ field: "aggregateType", renderer: OBJECT_RENDERER },
|
|
61
83
|
"aggregateId",
|
|
62
84
|
{ field: "createdBy", ...ACTOR_REFERENCE },
|
|
63
85
|
"id",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ColumnRendererProps } from "@cosmicdrift/kumiko-renderer";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
export declare function AuditEventCell({ value, row }: ColumnRendererProps): ReactNode;
|
|
4
|
+
export declare function AuditObjectCell({ row }: ColumnRendererProps): ReactNode;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Column renderers of the audit log, registered via `auditClient()`'s `columnRenderers`
|
|
2
|
+
// (screens.ts references them by name). The list frame stays the generic projectionList.
|
|
3
|
+
import { useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
4
|
+
import { auditEntityLabel, isAuditEventAction } from "./audit-event-labels.js";
|
|
5
|
+
export function AuditEventCell({ value, row }) {
|
|
6
|
+
const t = useTranslation();
|
|
7
|
+
const rawType = typeof value === "string" ? value : "";
|
|
8
|
+
const labelKey = row["eventLabelKey"];
|
|
9
|
+
if (typeof labelKey === "string")
|
|
10
|
+
return t(labelKey);
|
|
11
|
+
const action = row["eventAction"];
|
|
12
|
+
const entity = auditEntityLabel(t, row);
|
|
13
|
+
if (!isAuditEventAction(action) || entity === null)
|
|
14
|
+
return rawType;
|
|
15
|
+
return t("audit.log.event", { entity, action: t(`audit.log.action.${action}`) });
|
|
16
|
+
}
|
|
17
|
+
export function AuditObjectCell({ row }) {
|
|
18
|
+
const t = useTranslation();
|
|
19
|
+
return auditEntityLabel(t, row) ?? "";
|
|
20
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
2
|
+
import { type AuditEventAction } from "./constants.ts";
|
|
3
|
+
type Translate = ReturnType<typeof useTranslation>;
|
|
4
|
+
export declare function isAuditEventAction(value: unknown): value is AuditEventAction;
|
|
5
|
+
/** Singular entity noun: the entity's own `:noun` key, then the audit log's label for bundled entities, then the humanized aggregate type. */
|
|
6
|
+
export declare function auditEntityLabel(t: Translate, row: Readonly<Record<string, unknown>>): string | null;
|
|
7
|
+
export {};
|