strapi-cms-audit-log 1.1.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +212 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1026 -0
  4. package/dist/admin/App-BLt4lqEM.js +1284 -0
  5. package/dist/admin/App-ou5hy99f.mjs +1266 -0
  6. package/dist/admin/en-B0rPiE2W.mjs +87 -0
  7. package/dist/admin/en-BMlJxq3g.js +87 -0
  8. package/dist/admin/index-BYi8OPTw.js +80 -0
  9. package/dist/admin/index-DdmY-p3Q.mjs +81 -0
  10. package/dist/admin/index.js +4 -0
  11. package/dist/admin/index.mjs +4 -0
  12. package/dist/admin/src/components/AuditLogFilters.d.ts +18 -0
  13. package/dist/admin/src/components/AuditLogTable.d.ts +19 -0
  14. package/dist/admin/src/components/ChangeViewer.d.ts +14 -0
  15. package/dist/admin/src/components/JsonViewer.d.ts +20 -0
  16. package/dist/admin/src/components/PluginIcon.d.ts +3 -0
  17. package/dist/admin/src/components/WidgetDiff.d.ts +19 -0
  18. package/dist/admin/src/hooks/useAuditLogs.d.ts +36 -0
  19. package/dist/admin/src/index.d.ts +3 -0
  20. package/dist/admin/src/pages/App.d.ts +11 -0
  21. package/dist/admin/src/pages/AuditLogDetails.d.ts +10 -0
  22. package/dist/admin/src/pages/AuditLogs.d.ts +3 -0
  23. package/dist/admin/src/permissions.d.ts +26 -0
  24. package/dist/admin/src/pluginId.d.ts +2 -0
  25. package/dist/admin/src/types.d.ts +91 -0
  26. package/dist/admin/src/utils/format.d.ts +41 -0
  27. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  28. package/dist/admin/src/utils/widgets.d.ts +85 -0
  29. package/dist/server/index.js +1856 -0
  30. package/dist/server/index.mjs +1856 -0
  31. package/dist/server/src/bootstrap.d.ts +14 -0
  32. package/dist/server/src/config/index.d.ts +52 -0
  33. package/dist/server/src/constants.d.ts +140 -0
  34. package/dist/server/src/content-types/audit-log/index.d.ts +86 -0
  35. package/dist/server/src/content-types/audit-log/schema.d.ts +141 -0
  36. package/dist/server/src/content-types/index.d.ts +88 -0
  37. package/dist/server/src/controllers/audit-log.d.ts +31 -0
  38. package/dist/server/src/controllers/index.d.ts +42 -0
  39. package/dist/server/src/destroy.d.ts +19 -0
  40. package/dist/server/src/index.d.ts +328 -0
  41. package/dist/server/src/register.d.ts +31 -0
  42. package/dist/server/src/routes/admin.d.ts +26 -0
  43. package/dist/server/src/routes/index.d.ts +19 -0
  44. package/dist/server/src/services/access.d.ts +51 -0
  45. package/dist/server/src/services/audit.d.ts +25 -0
  46. package/dist/server/src/services/config.d.ts +30 -0
  47. package/dist/server/src/services/context.d.ts +33 -0
  48. package/dist/server/src/services/diff.d.ts +48 -0
  49. package/dist/server/src/services/immutability.d.ts +30 -0
  50. package/dist/server/src/services/index.d.ts +137 -0
  51. package/dist/server/src/services/retention.d.ts +22 -0
  52. package/dist/server/src/services/security.d.ts +54 -0
  53. package/dist/server/src/services/snapshot.d.ts +46 -0
  54. package/dist/server/src/services/tracker.d.ts +39 -0
  55. package/dist/server/src/types/index.d.ts +214 -0
  56. package/dist/server/src/utils/json.d.ts +17 -0
  57. package/dist/server/src/utils/paths.d.ts +34 -0
  58. package/dist/server/src/utils/sanitize.d.ts +16 -0
  59. package/package.json +112 -0
@@ -0,0 +1,1284 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const admin = require("@strapi/strapi/admin");
5
+ const reactRouterDom = require("react-router-dom");
6
+ const index = require("./index-BYi8OPTw.js");
7
+ const designSystem = require("@strapi/design-system");
8
+ const reactIntl = require("react-intl");
9
+ const React = require("react");
10
+ const icons = require("@strapi/icons");
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
14
+ if (e) {
15
+ for (const k in e) {
16
+ if (k !== "default") {
17
+ const d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: () => e[k]
21
+ });
22
+ }
23
+ }
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+ const React__namespace = /* @__PURE__ */ _interopNamespace(React);
29
+ const ACTION_COLORS = {
30
+ create: { background: "success100", text: "success600" },
31
+ update: { background: "primary100", text: "primary600" },
32
+ delete: { background: "danger100", text: "danger600" },
33
+ publish: { background: "success100", text: "success600" },
34
+ unpublish: { background: "warning100", text: "warning600" },
35
+ // Security actions. Green for a granted access, red for a refused one, and
36
+ // warning amber for the changes that alter who is able to do either — a role
37
+ // edit is not a failure, but it is the row a reviewer should stop on.
38
+ "login.success": { background: "success100", text: "success600" },
39
+ "login.failed": { background: "danger100", text: "danger600" },
40
+ logout: { background: "neutral150", text: "neutral700" },
41
+ "access.denied": { background: "danger100", text: "danger600" },
42
+ "admin.user.create": { background: "warning100", text: "warning600" },
43
+ "admin.user.update": { background: "warning100", text: "warning600" },
44
+ "admin.user.delete": { background: "danger100", text: "danger600" },
45
+ "admin.role.create": { background: "warning100", text: "warning600" },
46
+ "admin.role.update": { background: "warning100", text: "warning600" },
47
+ "admin.role.delete": { background: "danger100", text: "danger600" },
48
+ "admin.permission.create": { background: "warning100", text: "warning600" },
49
+ "admin.permission.update": { background: "warning100", text: "warning600" },
50
+ "admin.permission.delete": { background: "danger100", text: "danger600" },
51
+ "media.create": { background: "success100", text: "success600" },
52
+ "media.update": { background: "primary100", text: "primary600" },
53
+ "media.delete": { background: "danger100", text: "danger600" },
54
+ "media-folder.create": { background: "success100", text: "success600" },
55
+ "media-folder.update": { background: "primary100", text: "primary600" },
56
+ "media-folder.delete": { background: "danger100", text: "danger600" }
57
+ };
58
+ const actionColor = (action) => ACTION_COLORS[action] ?? { background: "neutral150", text: "neutral700" };
59
+ const shortContentTypeName = (uid) => {
60
+ const parts = uid.split(".");
61
+ return parts.length > 1 ? parts[parts.length - 1] : uid;
62
+ };
63
+ const formatValue = (value, maxLength = 220) => {
64
+ if (value === null) return "null";
65
+ if (value === void 0) return "empty";
66
+ if (typeof value === "boolean") return value ? "true" : "false";
67
+ if (typeof value === "string") {
68
+ return value.length > maxLength ? `${value.slice(0, maxLength)}…` : value || '""';
69
+ }
70
+ if (typeof value === "number") return String(value);
71
+ try {
72
+ const json = JSON.stringify(value, null, 2);
73
+ return json.length > maxLength ? `${json.slice(0, maxLength)}…` : json;
74
+ } catch {
75
+ return String(value);
76
+ }
77
+ };
78
+ const isStructured = (value) => value !== null && typeof value === "object";
79
+ const formatDate = (value, locale = "en") => {
80
+ if (!value) return "-";
81
+ const date = new Date(value);
82
+ if (Number.isNaN(date.getTime())) return "-";
83
+ return new Intl.DateTimeFormat(locale, {
84
+ day: "2-digit",
85
+ month: "short",
86
+ year: "numeric",
87
+ hour: "2-digit",
88
+ minute: "2-digit"
89
+ }).format(date);
90
+ };
91
+ const formatUser = (log) => log.userName ?? log.userEmail ?? (log.userId ? `#${log.userId}` : log.source);
92
+ const stringify = (value) => {
93
+ if (value === null || value === void 0) return "null";
94
+ try {
95
+ return JSON.stringify(value, null, 2) ?? String(value);
96
+ } catch {
97
+ return "/* value could not be serialised */";
98
+ }
99
+ };
100
+ const JsonViewer = ({ value, label, defaultOpen = false, maxHeight = "420px" }) => {
101
+ const { formatMessage } = reactIntl.useIntl();
102
+ const [isOpen, setIsOpen] = React__namespace.useState(defaultOpen);
103
+ const json = React__namespace.useMemo(() => stringify(value), [value]);
104
+ const lineCount = React__namespace.useMemo(() => json.split("\n").length, [json]);
105
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
106
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 2, children: [
107
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h3", children: label }),
108
+ /* @__PURE__ */ jsxRuntime.jsx(
109
+ designSystem.Button,
110
+ {
111
+ variant: "tertiary",
112
+ size: "S",
113
+ startIcon: isOpen ? /* @__PURE__ */ jsxRuntime.jsx(icons.CaretUp, {}) : /* @__PURE__ */ jsxRuntime.jsx(icons.CaretDown, {}),
114
+ onClick: () => setIsOpen((open) => !open),
115
+ children: isOpen ? formatMessage({ id: index.getTranslation("json.hide"), defaultMessage: "Hide" }) : formatMessage(
116
+ { id: index.getTranslation("json.show"), defaultMessage: "Show ({count} lines)" },
117
+ { count: lineCount }
118
+ )
119
+ }
120
+ )
121
+ ] }),
122
+ isOpen ? /* @__PURE__ */ jsxRuntime.jsx(
123
+ designSystem.Box,
124
+ {
125
+ background: "neutral100",
126
+ hasRadius: true,
127
+ padding: 4,
128
+ style: { maxHeight, overflow: "auto" },
129
+ borderColor: "neutral200",
130
+ children: /* @__PURE__ */ jsxRuntime.jsx(
131
+ designSystem.Typography,
132
+ {
133
+ tag: "pre",
134
+ variant: "pi",
135
+ style: {
136
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace',
137
+ whiteSpace: "pre",
138
+ margin: 0
139
+ },
140
+ children: json
141
+ }
142
+ )
143
+ }
144
+ ) : null
145
+ ] });
146
+ };
147
+ const ChangeRow = ({ path, from, to }) => {
148
+ const { formatMessage } = reactIntl.useIntl();
149
+ const structured = isStructured(from) || isStructured(to);
150
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 4, paddingBottom: 4, children: [
151
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: path }),
152
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
153
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "danger100", hasRadius: true, padding: 3, children: [
154
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "danger600", children: formatMessage({ id: index.getTranslation("changes.before"), defaultMessage: "Before" }) }),
155
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: formatValue(from) }) })
156
+ ] }),
157
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "success100", hasRadius: true, padding: 3, children: [
158
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "success600", children: formatMessage({ id: index.getTranslation("changes.after"), defaultMessage: "After" }) }),
159
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { tag: "p", style: { whiteSpace: "pre-wrap", wordBreak: "break-word" }, children: formatValue(to) }) })
160
+ ] })
161
+ ] }) }),
162
+ structured ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 3, children: /* @__PURE__ */ jsxRuntime.jsx(
163
+ JsonViewer,
164
+ {
165
+ label: formatMessage(
166
+ { id: index.getTranslation("changes.raw"), defaultMessage: "Raw value for {path}" },
167
+ { path }
168
+ ),
169
+ value: { from, to }
170
+ }
171
+ ) }) : null
172
+ ] });
173
+ };
174
+ const ChangeViewer = ({ changes }) => {
175
+ const { formatMessage } = reactIntl.useIntl();
176
+ const entries = React__namespace.useMemo(
177
+ () => Object.entries(changes ?? {}).sort(([a], [b]) => a.split(".").length - b.split(".").length || a.localeCompare(b)),
178
+ [changes]
179
+ );
180
+ if (entries.length === 0) {
181
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatMessage({
182
+ id: index.getTranslation("changes.empty"),
183
+ defaultMessage: "No field-level changes were recorded for this operation. This is expected for creates and deletes, and for updates that re-saved a document without modifying it."
184
+ }) });
185
+ }
186
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
187
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 2, children: [
188
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: formatMessage({ id: index.getTranslation("changes.title"), defaultMessage: "Changes" }) }),
189
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: entries.length })
190
+ ] }),
191
+ entries.map(([path, change], index2) => /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
192
+ index2 > 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}) : null,
193
+ /* @__PURE__ */ jsxRuntime.jsx(ChangeRow, { path, from: change?.from, to: change?.to })
194
+ ] }, path))
195
+ ] });
196
+ };
197
+ const STRUCTURAL_KEYS = /* @__PURE__ */ new Set(["id", "__component", "__temp_key__", "createdAt", "updatedAt"]);
198
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
199
+ const isWidgetArray = (value) => Array.isArray(value) && value.length > 0 && value.every((item) => isObject(item) && typeof item.__component === "string");
200
+ const isEqual = (a, b) => {
201
+ if (a === b) return true;
202
+ if (a == null && b == null) return true;
203
+ if (a == null || b == null) return false;
204
+ if (a instanceof Date || b instanceof Date) {
205
+ const left = a instanceof Date ? a.getTime() : Date.parse(String(a));
206
+ const right = b instanceof Date ? b.getTime() : Date.parse(String(b));
207
+ return Number.isFinite(left) && Number.isFinite(right) && left === right;
208
+ }
209
+ if (Array.isArray(a) || Array.isArray(b)) {
210
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
211
+ return a.every((item, index2) => isEqual(item, b[index2]));
212
+ }
213
+ if (isObject(a) && isObject(b)) {
214
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
215
+ for (const key of keys) {
216
+ if (STRUCTURAL_KEYS.has(key)) continue;
217
+ if (!isEqual(a[key], b[key])) return false;
218
+ }
219
+ return true;
220
+ }
221
+ return false;
222
+ };
223
+ const diffFields = (before, after, prefix = "") => {
224
+ const paths = [];
225
+ const keys = /* @__PURE__ */ new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})]);
226
+ for (const key of keys) {
227
+ if (prefix === "" && STRUCTURAL_KEYS.has(key)) continue;
228
+ const left = before?.[key];
229
+ const right = after?.[key];
230
+ if (isEqual(left, right)) continue;
231
+ const path = prefix ? `${prefix}.${key}` : key;
232
+ if (isObject(left) && isObject(right)) {
233
+ paths.push(...diffFields(left, right, path));
234
+ } else {
235
+ paths.push(path);
236
+ }
237
+ }
238
+ return paths;
239
+ };
240
+ const pairSlots = (before, after) => {
241
+ const length = Math.max(before.length, after.length);
242
+ const slots = [];
243
+ for (let index2 = 0; index2 < length; index2 += 1) {
244
+ const left = before[index2] ?? null;
245
+ const right = after[index2] ?? null;
246
+ let status;
247
+ if (!left) status = "added";
248
+ else if (!right) status = "removed";
249
+ else if (isEqual(left, right)) status = "unchanged";
250
+ else status = "changed";
251
+ const sameComponent = left && right && left.__component === right.__component;
252
+ slots.push({
253
+ index: index2,
254
+ status,
255
+ component: right?.__component ?? left?.__component ?? "unknown",
256
+ // Only meaningful when the slot held one widget and now holds another —
257
+ // otherwise the header would repeat the same name twice.
258
+ replacedComponent: left && right && !sameComponent ? left.__component : null,
259
+ before: left,
260
+ after: right,
261
+ changedFields: status === "changed" ? diffFields(left, right) : []
262
+ });
263
+ }
264
+ return slots;
265
+ };
266
+ const extractWidgetZones = (before, after) => {
267
+ const zones = [];
268
+ const names = /* @__PURE__ */ new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})]);
269
+ for (const name of names) {
270
+ const left = before?.[name];
271
+ const right = after?.[name];
272
+ if (!isWidgetArray(left) && !isWidgetArray(right)) continue;
273
+ const slots = pairSlots(
274
+ isWidgetArray(left) ? left : [],
275
+ isWidgetArray(right) ? right : []
276
+ );
277
+ zones.push({
278
+ name,
279
+ slots,
280
+ changedCount: slots.filter((slot) => slot.status !== "unchanged").length
281
+ });
282
+ }
283
+ return zones.sort((a, b) => a.name.localeCompare(b.name));
284
+ };
285
+ const widgetLabel = (component) => {
286
+ const name = component.includes(".") ? component.slice(component.indexOf(".") + 1) : component;
287
+ return name.split("-").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
288
+ };
289
+ const widgetRows = (widget, prefix = "") => {
290
+ if (!widget) return [];
291
+ const rows = [];
292
+ for (const [key, value] of Object.entries(widget)) {
293
+ if (prefix === "" && STRUCTURAL_KEYS.has(key)) continue;
294
+ const path = prefix ? `${prefix}.${key}` : key;
295
+ if (isObject(value) && !(value instanceof Date)) {
296
+ const nested = widgetRows(value, path);
297
+ if (nested.length > 0) rows.push(...nested);
298
+ else rows.push({ path, value });
299
+ continue;
300
+ }
301
+ rows.push({ path, value });
302
+ }
303
+ return rows;
304
+ };
305
+ const STATUS_STYLE = {
306
+ changed: { background: "primary100", text: "primary600", border: "primary200" },
307
+ added: { background: "success100", text: "success600", border: "success200" },
308
+ removed: { background: "danger100", text: "danger600", border: "danger200" },
309
+ unchanged: { background: "neutral150", text: "neutral600", border: "neutral200" }
310
+ };
311
+ const COLLAPSE_AFTER_ROWS = 12;
312
+ const WidgetCard = ({ side, widget, changedFields, emptyLabel }) => {
313
+ const { formatMessage } = reactIntl.useIntl();
314
+ const [showAll, setShowAll] = React__namespace.useState(false);
315
+ const rows = React__namespace.useMemo(() => widgetRows(widget), [widget]);
316
+ const changed = React__namespace.useMemo(() => new Set(changedFields), [changedFields]);
317
+ const isBefore = side === "before";
318
+ const tint = isBefore ? { background: "danger100", text: "danger600", border: "danger200" } : { background: "success100", text: "success600", border: "success200" };
319
+ const visible = showAll ? rows : rows.slice(0, COLLAPSE_AFTER_ROWS);
320
+ const hidden = rows.length - visible.length;
321
+ return /* @__PURE__ */ jsxRuntime.jsxs(
322
+ designSystem.Box,
323
+ {
324
+ background: tint.background,
325
+ hasRadius: true,
326
+ padding: 4,
327
+ borderColor: tint.border,
328
+ borderWidth: "1px",
329
+ borderStyle: "solid",
330
+ children: [
331
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 2, children: [
332
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: tint.text, children: isBefore ? formatMessage({ id: index.getTranslation("widgets.before"), defaultMessage: "Before" }) : formatMessage({ id: index.getTranslation("widgets.after"), defaultMessage: "After" }) }),
333
+ rows.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage(
334
+ { id: index.getTranslation("widgets.fieldCount"), defaultMessage: "{count} fields" },
335
+ { count: rows.length }
336
+ ) }) : null
337
+ ] }),
338
+ widget === null ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", style: { fontStyle: "italic" }, children: emptyLabel }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
339
+ visible.map(({ path, value }) => {
340
+ const isChanged = changed.has(path);
341
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "flex-start", gap: 3, children: [
342
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { minWidth: "30%", maxWidth: "30%" }, children: /* @__PURE__ */ jsxRuntime.jsx(
343
+ designSystem.Typography,
344
+ {
345
+ variant: "pi",
346
+ fontWeight: isChanged ? "bold" : "regular",
347
+ textColor: isChanged ? tint.text : "neutral600",
348
+ style: { wordBreak: "break-word" },
349
+ children: path
350
+ }
351
+ ) }),
352
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { flex: 1 }, children: /* @__PURE__ */ jsxRuntime.jsx(
353
+ designSystem.Typography,
354
+ {
355
+ variant: "pi",
356
+ tag: "p",
357
+ fontWeight: isChanged ? "bold" : "regular",
358
+ textColor: "neutral800",
359
+ style: { whiteSpace: "pre-wrap", wordBreak: "break-word", margin: 0 },
360
+ children: formatValue(value, 320)
361
+ }
362
+ ) })
363
+ ] }, path);
364
+ }),
365
+ hidden > 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "S", onClick: () => setShowAll(true), children: formatMessage(
366
+ {
367
+ id: index.getTranslation("widgets.showAllFields"),
368
+ defaultMessage: "Show {count} more fields"
369
+ },
370
+ { count: hidden }
371
+ ) }) }) : null
372
+ ] })
373
+ ]
374
+ }
375
+ );
376
+ };
377
+ const WidgetSlotView = ({ slot }) => {
378
+ const { formatMessage } = reactIntl.useIntl();
379
+ const [isOpen, setIsOpen] = React__namespace.useState(slot.status !== "unchanged");
380
+ const style = STATUS_STYLE[slot.status];
381
+ const statusLabel = formatMessage({
382
+ id: index.getTranslation(`widgets.status.${slot.status}`),
383
+ defaultMessage: slot.status
384
+ });
385
+ return /* @__PURE__ */ jsxRuntime.jsxs(
386
+ designSystem.Box,
387
+ {
388
+ hasRadius: true,
389
+ background: "neutral0",
390
+ borderColor: style.border,
391
+ borderWidth: "1px",
392
+ borderStyle: "solid",
393
+ padding: 4,
394
+ children: [
395
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", gap: 3, children: [
396
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, style: { minWidth: 0 }, children: [
397
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: `#${slot.index}` }),
398
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h4", style: { wordBreak: "break-word" }, children: widgetLabel(slot.component) }),
399
+ /* @__PURE__ */ jsxRuntime.jsx(
400
+ designSystem.Typography,
401
+ {
402
+ variant: "pi",
403
+ textColor: "neutral500",
404
+ style: {
405
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace'
406
+ },
407
+ children: slot.component
408
+ }
409
+ )
410
+ ] }),
411
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
412
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: style.background, textColor: style.text, children: statusLabel }),
413
+ /* @__PURE__ */ jsxRuntime.jsx(
414
+ designSystem.Button,
415
+ {
416
+ variant: "tertiary",
417
+ size: "S",
418
+ startIcon: isOpen ? /* @__PURE__ */ jsxRuntime.jsx(icons.CaretUp, {}) : /* @__PURE__ */ jsxRuntime.jsx(icons.CaretDown, {}),
419
+ onClick: () => setIsOpen((open) => !open),
420
+ children: isOpen ? formatMessage({ id: index.getTranslation("widgets.hide"), defaultMessage: "Hide" }) : formatMessage({ id: index.getTranslation("widgets.show"), defaultMessage: "Show" })
421
+ }
422
+ )
423
+ ] })
424
+ ] }),
425
+ slot.replacedComponent ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "warning600", children: formatMessage(
426
+ {
427
+ id: index.getTranslation("widgets.replaced"),
428
+ defaultMessage: "This slot previously held a different widget ({component})."
429
+ },
430
+ { component: slot.replacedComponent }
431
+ ) }) }) : null,
432
+ isOpen ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 3, children: [
433
+ slot.status !== "added" ? /* @__PURE__ */ jsxRuntime.jsx(
434
+ WidgetCard,
435
+ {
436
+ side: "before",
437
+ widget: slot.before,
438
+ changedFields: slot.changedFields,
439
+ emptyLabel: formatMessage({
440
+ id: index.getTranslation("widgets.noBefore"),
441
+ defaultMessage: "This widget did not exist before the change."
442
+ })
443
+ }
444
+ ) : null,
445
+ slot.status !== "removed" ? /* @__PURE__ */ jsxRuntime.jsx(
446
+ WidgetCard,
447
+ {
448
+ side: "after",
449
+ widget: slot.after,
450
+ changedFields: slot.changedFields,
451
+ emptyLabel: formatMessage({
452
+ id: index.getTranslation("widgets.noAfter"),
453
+ defaultMessage: "This widget was removed by the change."
454
+ })
455
+ }
456
+ ) : null,
457
+ /* @__PURE__ */ jsxRuntime.jsx(
458
+ JsonViewer,
459
+ {
460
+ label: formatMessage(
461
+ { id: index.getTranslation("widgets.raw"), defaultMessage: "Raw JSON for slot {index}" },
462
+ { index: slot.index }
463
+ ),
464
+ value: { before: slot.before, after: slot.after }
465
+ }
466
+ )
467
+ ] }) }) : null
468
+ ]
469
+ }
470
+ );
471
+ };
472
+ const WidgetDiff = ({ before, after }) => {
473
+ const { formatMessage } = reactIntl.useIntl();
474
+ const [showUnchanged, setShowUnchanged] = React__namespace.useState(false);
475
+ const zones = React__namespace.useMemo(() => extractWidgetZones(before, after), [before, after]);
476
+ if (zones.length === 0) return null;
477
+ return (
478
+ // The component owns its card rather than being wrapped by the page: it
479
+ // renders nothing when there is no dynamic zone, and a caller-supplied
480
+ // wrapper would leave an empty card behind on every content type without one.
481
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 6, children: [
482
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 2, children: [
483
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: formatMessage({ id: index.getTranslation("widgets.title"), defaultMessage: "Widgets" }) }),
484
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "S", onClick: () => setShowUnchanged((show) => !show), children: showUnchanged ? formatMessage({
485
+ id: index.getTranslation("widgets.hideUnchanged"),
486
+ defaultMessage: "Hide unchanged widgets"
487
+ }) : formatMessage({
488
+ id: index.getTranslation("widgets.showUnchanged"),
489
+ defaultMessage: "Show unchanged widgets"
490
+ }) })
491
+ ] }),
492
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
493
+ id: index.getTranslation("widgets.hint"),
494
+ defaultMessage: "Each changed widget is shown twice — once with the data it held before this operation, and once with the data it holds after. Fields that differ are highlighted in both copies."
495
+ }) }) }),
496
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: zones.map((zone) => {
497
+ const visible = showUnchanged ? zone.slots : zone.slots.filter((slot) => slot.status !== "unchanged");
498
+ const hiddenCount = zone.slots.length - visible.length;
499
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
500
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, paddingBottom: 3, children: [
501
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: zone.name }),
502
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: formatMessage(
503
+ {
504
+ id: index.getTranslation("widgets.zoneSummary"),
505
+ defaultMessage: "{changed} changed of {total}"
506
+ },
507
+ { changed: zone.changedCount, total: zone.slots.length }
508
+ ) })
509
+ ] }),
510
+ visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: formatMessage(
511
+ {
512
+ id: index.getTranslation("widgets.allUnchanged"),
513
+ defaultMessage: "No widget in this zone changed. {count} unchanged widgets are hidden."
514
+ },
515
+ { count: hiddenCount }
516
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 3, children: visible.map((slot) => /* @__PURE__ */ jsxRuntime.jsx(WidgetSlotView, { slot }, `${zone.name}-${slot.index}`)) })
517
+ ] }, zone.name);
518
+ }) })
519
+ ] })
520
+ );
521
+ };
522
+ const EMPTY_PAGINATION = { page: 1, pageSize: 20, pageCount: 0, total: 0 };
523
+ const EMPTY_FILTERS = {
524
+ contentTypes: [],
525
+ users: [],
526
+ locales: [],
527
+ actions: [],
528
+ sources: [],
529
+ outcomes: []
530
+ };
531
+ const message = (error) => error?.response?.data?.error?.message ?? error?.message ?? "Something went wrong while loading audit logs.";
532
+ const useAuditLogs = (search) => {
533
+ const { get } = admin.useFetchClient();
534
+ const [data, setData] = React__namespace.useState([]);
535
+ const [pagination, setPagination] = React__namespace.useState(EMPTY_PAGINATION);
536
+ const [isLoading, setIsLoading] = React__namespace.useState(true);
537
+ const [error, setError] = React__namespace.useState(null);
538
+ const [refreshToken, setRefreshToken] = React__namespace.useState(0);
539
+ const refresh = React__namespace.useCallback(() => setRefreshToken((token) => token + 1), []);
540
+ React__namespace.useEffect(() => {
541
+ let cancelled = false;
542
+ const load = async () => {
543
+ setIsLoading(true);
544
+ setError(null);
545
+ try {
546
+ const { data: response } = await get(
547
+ `/${index.PLUGIN_ID}/logs${search ? `?${search}` : ""}`
548
+ );
549
+ if (cancelled) return;
550
+ setData(response.results ?? []);
551
+ setPagination(response.pagination ?? EMPTY_PAGINATION);
552
+ } catch (err) {
553
+ if (cancelled) return;
554
+ setError(message(err));
555
+ setData([]);
556
+ setPagination(EMPTY_PAGINATION);
557
+ } finally {
558
+ if (!cancelled) setIsLoading(false);
559
+ }
560
+ };
561
+ void load();
562
+ return () => {
563
+ cancelled = true;
564
+ };
565
+ }, [get, search, refreshToken]);
566
+ return { data, pagination, isLoading, error, refresh };
567
+ };
568
+ const useAuditFilterOptions = () => {
569
+ const { get } = admin.useFetchClient();
570
+ const [options, setOptions] = React__namespace.useState(EMPTY_FILTERS);
571
+ const [isLoading, setIsLoading] = React__namespace.useState(true);
572
+ React__namespace.useEffect(() => {
573
+ let cancelled = false;
574
+ const load = async () => {
575
+ try {
576
+ const { data } = await get(`/${index.PLUGIN_ID}/filters`);
577
+ if (!cancelled) setOptions(data.data ?? EMPTY_FILTERS);
578
+ } catch {
579
+ if (!cancelled) setOptions(EMPTY_FILTERS);
580
+ } finally {
581
+ if (!cancelled) setIsLoading(false);
582
+ }
583
+ };
584
+ void load();
585
+ return () => {
586
+ cancelled = true;
587
+ };
588
+ }, [get]);
589
+ return { options, isLoading };
590
+ };
591
+ const useAuditLog = (id) => {
592
+ const { get } = admin.useFetchClient();
593
+ const [data, setData] = React__namespace.useState(null);
594
+ const [isLoading, setIsLoading] = React__namespace.useState(true);
595
+ const [error, setError] = React__namespace.useState(null);
596
+ React__namespace.useEffect(() => {
597
+ let cancelled = false;
598
+ if (!id) {
599
+ setError("Missing audit log id.");
600
+ setIsLoading(false);
601
+ return;
602
+ }
603
+ const load = async () => {
604
+ setIsLoading(true);
605
+ setError(null);
606
+ try {
607
+ const { data: response } = await get(`/${index.PLUGIN_ID}/logs/${id}`);
608
+ if (!cancelled) setData(response.data);
609
+ } catch (err) {
610
+ if (!cancelled) {
611
+ setError(message(err));
612
+ setData(null);
613
+ }
614
+ } finally {
615
+ if (!cancelled) setIsLoading(false);
616
+ }
617
+ };
618
+ void load();
619
+ return () => {
620
+ cancelled = true;
621
+ };
622
+ }, [get, id]);
623
+ return { data, isLoading, error };
624
+ };
625
+ const useDeleteAuditLog = () => {
626
+ const { del } = admin.useFetchClient();
627
+ return React__namespace.useCallback(
628
+ async (id) => {
629
+ await del(`/${index.PLUGIN_ID}/logs/${id}`);
630
+ },
631
+ [del]
632
+ );
633
+ };
634
+ const MetaField = ({ label, value, mono = false }) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Item, { col: 4, s: 6, xs: 12, direction: "column", alignItems: "flex-start", gap: 1, children: [
635
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: label }),
636
+ /* @__PURE__ */ jsxRuntime.jsx(
637
+ designSystem.Typography,
638
+ {
639
+ textColor: "neutral800",
640
+ style: {
641
+ wordBreak: "break-word",
642
+ ...mono ? {
643
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace'
644
+ } : {}
645
+ },
646
+ children: value ?? "-"
647
+ }
648
+ )
649
+ ] });
650
+ const AuditLogDetails = () => {
651
+ const { formatMessage, locale } = reactIntl.useIntl();
652
+ const { id } = reactRouterDom.useParams();
653
+ const { data: log, isLoading, error } = useAuditLog(id);
654
+ if (isLoading) return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, {});
655
+ if (error || !log) {
656
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, { content: error ?? void 0 });
657
+ }
658
+ const colors = actionColor(log.action);
659
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
660
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: formatMessage({ id: index.getTranslation("detail.title"), defaultMessage: "Audit Log" }) }),
661
+ /* @__PURE__ */ jsxRuntime.jsx(
662
+ admin.Layouts.Header,
663
+ {
664
+ navigationAction: /* @__PURE__ */ jsxRuntime.jsx(admin.BackButton, { fallback: ".." }),
665
+ title: `${log.action} · ${log.contentTypeDisplayName || shortContentTypeName(log.contentType)}`,
666
+ subtitle: formatDate(log.createdAt, locale)
667
+ }
668
+ ),
669
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
670
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 6, children: [
671
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: formatMessage({ id: index.getTranslation("detail.overview"), defaultMessage: "Overview" }) }),
672
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 5, children: [
673
+ /* @__PURE__ */ jsxRuntime.jsx(
674
+ MetaField,
675
+ {
676
+ label: formatMessage({
677
+ id: index.getTranslation("detail.action"),
678
+ defaultMessage: "Action"
679
+ }),
680
+ value: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: colors.background, textColor: colors.text, children: log.action })
681
+ }
682
+ ),
683
+ /* @__PURE__ */ jsxRuntime.jsx(
684
+ MetaField,
685
+ {
686
+ label: formatMessage({
687
+ id: index.getTranslation("detail.outcome"),
688
+ defaultMessage: "Outcome"
689
+ }),
690
+ value: /* @__PURE__ */ jsxRuntime.jsx(
691
+ designSystem.Badge,
692
+ {
693
+ backgroundColor: log.outcome === "failure" ? "danger100" : "success100",
694
+ textColor: log.outcome === "failure" ? "danger600" : "success600",
695
+ children: log.outcome ?? "success"
696
+ }
697
+ )
698
+ }
699
+ ),
700
+ /* @__PURE__ */ jsxRuntime.jsx(
701
+ MetaField,
702
+ {
703
+ label: formatMessage({ id: index.getTranslation("detail.user"), defaultMessage: "User" }),
704
+ value: formatUser(log)
705
+ }
706
+ ),
707
+ /* @__PURE__ */ jsxRuntime.jsx(
708
+ MetaField,
709
+ {
710
+ label: formatMessage({
711
+ id: index.getTranslation("detail.userEmail"),
712
+ defaultMessage: "User email"
713
+ }),
714
+ value: log.userEmail
715
+ }
716
+ ),
717
+ /* @__PURE__ */ jsxRuntime.jsx(
718
+ MetaField,
719
+ {
720
+ label: formatMessage({ id: index.getTranslation("detail.date"), defaultMessage: "Date" }),
721
+ value: formatDate(log.createdAt, locale)
722
+ }
723
+ ),
724
+ /* @__PURE__ */ jsxRuntime.jsx(
725
+ MetaField,
726
+ {
727
+ label: formatMessage({
728
+ id: index.getTranslation("detail.contentType"),
729
+ defaultMessage: "Content type"
730
+ }),
731
+ value: log.contentType,
732
+ mono: true
733
+ }
734
+ ),
735
+ /* @__PURE__ */ jsxRuntime.jsx(
736
+ MetaField,
737
+ {
738
+ label: formatMessage({
739
+ id: index.getTranslation("detail.documentId"),
740
+ defaultMessage: "Document ID"
741
+ }),
742
+ value: log.contentDocumentId,
743
+ mono: true
744
+ }
745
+ ),
746
+ /* @__PURE__ */ jsxRuntime.jsx(
747
+ MetaField,
748
+ {
749
+ label: formatMessage({
750
+ id: index.getTranslation("detail.entryId"),
751
+ defaultMessage: "Entry ID"
752
+ }),
753
+ value: log.contentId,
754
+ mono: true
755
+ }
756
+ ),
757
+ /* @__PURE__ */ jsxRuntime.jsx(
758
+ MetaField,
759
+ {
760
+ label: formatMessage({
761
+ id: index.getTranslation("detail.locale"),
762
+ defaultMessage: "Locale"
763
+ }),
764
+ value: log.locale
765
+ }
766
+ ),
767
+ /* @__PURE__ */ jsxRuntime.jsx(
768
+ MetaField,
769
+ {
770
+ label: formatMessage({
771
+ id: index.getTranslation("detail.source"),
772
+ defaultMessage: "Source"
773
+ }),
774
+ value: log.source
775
+ }
776
+ ),
777
+ /* @__PURE__ */ jsxRuntime.jsx(
778
+ MetaField,
779
+ {
780
+ label: formatMessage({
781
+ id: index.getTranslation("detail.ipAddress"),
782
+ defaultMessage: "IP address"
783
+ }),
784
+ value: log.ipAddress,
785
+ mono: true
786
+ }
787
+ ),
788
+ /* @__PURE__ */ jsxRuntime.jsx(
789
+ MetaField,
790
+ {
791
+ label: formatMessage({
792
+ id: index.getTranslation("detail.requestId"),
793
+ defaultMessage: "Request ID"
794
+ }),
795
+ value: log.requestId,
796
+ mono: true
797
+ }
798
+ ),
799
+ /* @__PURE__ */ jsxRuntime.jsx(
800
+ MetaField,
801
+ {
802
+ label: formatMessage({
803
+ id: index.getTranslation("detail.userAgent"),
804
+ defaultMessage: "User agent"
805
+ }),
806
+ value: log.userAgent,
807
+ mono: true
808
+ }
809
+ )
810
+ ] }) })
811
+ ] }),
812
+ log.metadata && Object.keys(log.metadata).length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 6, children: [
813
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: formatMessage({
814
+ id: index.getTranslation("detail.metadata"),
815
+ defaultMessage: "Event detail"
816
+ }) }),
817
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { gap: 5, children: Object.entries(log.metadata).map(([key, value]) => /* @__PURE__ */ jsxRuntime.jsx(
818
+ MetaField,
819
+ {
820
+ label: key,
821
+ value: value === null || value === void 0 ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value),
822
+ mono: key === "path" || key === "attemptedEmail"
823
+ },
824
+ key
825
+ )) }) })
826
+ ] }) : null,
827
+ /* @__PURE__ */ jsxRuntime.jsx(WidgetDiff, { before: log.before, after: log.after }),
828
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(ChangeViewer, { changes: log.changes }) }),
829
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 6, children: [
830
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", tag: "h2", children: formatMessage({
831
+ id: index.getTranslation("detail.snapshots"),
832
+ defaultMessage: "Raw snapshots"
833
+ }) }),
834
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
835
+ id: index.getTranslation("detail.snapshotsHint"),
836
+ defaultMessage: "The document state either side of this operation, with configured sensitive fields removed. A missing snapshot means the state did not exist (a create has no before, a delete has no after) or storage for it is disabled in the plugin configuration."
837
+ }) }) }),
838
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
839
+ /* @__PURE__ */ jsxRuntime.jsx(
840
+ JsonViewer,
841
+ {
842
+ label: formatMessage({
843
+ id: index.getTranslation("detail.before"),
844
+ defaultMessage: "Before"
845
+ }),
846
+ value: log.before
847
+ }
848
+ ),
849
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
850
+ /* @__PURE__ */ jsxRuntime.jsx(
851
+ JsonViewer,
852
+ {
853
+ label: formatMessage({ id: index.getTranslation("detail.after"), defaultMessage: "After" }),
854
+ value: log.after
855
+ }
856
+ )
857
+ ] })
858
+ ] })
859
+ ] }) })
860
+ ] });
861
+ };
862
+ const ALL = "__all__";
863
+ const FILTER_KEYS = [
864
+ "action",
865
+ "contentType",
866
+ "userId",
867
+ "locale",
868
+ "source",
869
+ "outcome",
870
+ "contentDocumentId",
871
+ "dateFrom",
872
+ "dateTo"
873
+ ];
874
+ const toDate = (value) => {
875
+ if (!value) return void 0;
876
+ const date = new Date(value);
877
+ return Number.isNaN(date.getTime()) ? void 0 : date;
878
+ };
879
+ const AuditLogFilters = ({ options, query, onChange, onClear }) => {
880
+ const { formatMessage } = reactIntl.useIntl();
881
+ const hasActiveFilter = FILTER_KEYS.some((key) => Boolean(query[key]));
882
+ const select = (key) => (value) => onChange({ [key]: value === ALL ? void 0 : String(value) });
883
+ const combo = (key) => (value) => onChange({ [key]: value ? String(value) : void 0 });
884
+ const dateChange = (key) => (date) => {
885
+ if (!date) {
886
+ onChange({ [key]: void 0 });
887
+ return;
888
+ }
889
+ const bounded = new Date(date);
890
+ if (key === "dateFrom") bounded.setHours(0, 0, 0, 0);
891
+ else bounded.setHours(23, 59, 59, 999);
892
+ onChange({ [key]: bounded.toISOString() });
893
+ };
894
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 4, marginBottom: 4, children: [
895
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 4, children: [
896
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "action", children: [
897
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTranslation("filters.action"), defaultMessage: "Action" }) }),
898
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelect, { value: query.action ?? ALL, onChange: select("action"), children: [
899
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: ALL, children: formatMessage({ id: index.getTranslation("filters.all"), defaultMessage: "All" }) }),
900
+ options.actions.map((action) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: action, children: action }, action))
901
+ ] })
902
+ ] }) }),
903
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "contentType", children: [
904
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
905
+ id: index.getTranslation("filters.contentType"),
906
+ defaultMessage: "Content type"
907
+ }) }),
908
+ /* @__PURE__ */ jsxRuntime.jsx(
909
+ designSystem.Combobox,
910
+ {
911
+ value: query.contentType ?? "",
912
+ onChange: combo("contentType"),
913
+ onClear: () => onChange({ contentType: void 0 }),
914
+ placeholder: formatMessage({
915
+ id: index.getTranslation("filters.all"),
916
+ defaultMessage: "All"
917
+ }),
918
+ children: options.contentTypes.map(({ uid, displayName }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: uid, children: displayName || shortContentTypeName(uid) }, uid))
919
+ }
920
+ )
921
+ ] }) }),
922
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "userId", children: [
923
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTranslation("filters.user"), defaultMessage: "User" }) }),
924
+ /* @__PURE__ */ jsxRuntime.jsx(
925
+ designSystem.Combobox,
926
+ {
927
+ value: query.userId ?? "",
928
+ onChange: combo("userId"),
929
+ onClear: () => onChange({ userId: void 0 }),
930
+ placeholder: formatMessage({
931
+ id: index.getTranslation("filters.all"),
932
+ defaultMessage: "All"
933
+ }),
934
+ children: options.users.map(({ userId, label }) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: userId, children: label }, userId))
935
+ }
936
+ )
937
+ ] }) }),
938
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "locale", children: [
939
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTranslation("filters.locale"), defaultMessage: "Locale" }) }),
940
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelect, { value: query.locale ?? ALL, onChange: select("locale"), children: [
941
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: ALL, children: formatMessage({ id: index.getTranslation("filters.all"), defaultMessage: "All" }) }),
942
+ options.locales.map((locale) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: locale, children: locale }, locale))
943
+ ] })
944
+ ] }) }),
945
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "source", children: [
946
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTranslation("filters.source"), defaultMessage: "Source" }) }),
947
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelect, { value: query.source ?? ALL, onChange: select("source"), children: [
948
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: ALL, children: formatMessage({ id: index.getTranslation("filters.all"), defaultMessage: "All" }) }),
949
+ options.sources.map((source) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: source, children: source }, source))
950
+ ] })
951
+ ] }) }),
952
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "outcome", children: [
953
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTranslation("filters.outcome"), defaultMessage: "Outcome" }) }),
954
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.SingleSelect, { value: query.outcome ?? ALL, onChange: select("outcome"), children: [
955
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: ALL, children: formatMessage({ id: index.getTranslation("filters.all"), defaultMessage: "All" }) }),
956
+ options.outcomes.map((outcome) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: outcome, children: outcome }, outcome))
957
+ ] })
958
+ ] }) }),
959
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "contentDocumentId", children: [
960
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({
961
+ id: index.getTranslation("filters.documentId"),
962
+ defaultMessage: "Document ID"
963
+ }) }),
964
+ /* @__PURE__ */ jsxRuntime.jsx(
965
+ designSystem.TextInput,
966
+ {
967
+ placeholder: formatMessage({
968
+ id: index.getTranslation("filters.documentIdPlaceholder"),
969
+ defaultMessage: "Exact document id"
970
+ }),
971
+ value: query.contentDocumentId ?? "",
972
+ onChange: (event) => onChange({ contentDocumentId: event.target.value || void 0 })
973
+ }
974
+ )
975
+ ] }) }),
976
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "dateFrom", children: [
977
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTranslation("filters.dateFrom"), defaultMessage: "From" }) }),
978
+ /* @__PURE__ */ jsxRuntime.jsx(
979
+ designSystem.DatePicker,
980
+ {
981
+ value: toDate(query.dateFrom),
982
+ onChange: dateChange("dateFrom"),
983
+ onClear: () => onChange({ dateFrom: void 0 }),
984
+ clearLabel: formatMessage({
985
+ id: index.getTranslation("filters.clearDate"),
986
+ defaultMessage: "Clear date"
987
+ })
988
+ }
989
+ )
990
+ ] }) }),
991
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, s: 6, xs: 12, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "dateTo", children: [
992
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: formatMessage({ id: index.getTranslation("filters.dateTo"), defaultMessage: "To" }) }),
993
+ /* @__PURE__ */ jsxRuntime.jsx(
994
+ designSystem.DatePicker,
995
+ {
996
+ value: toDate(query.dateTo),
997
+ onChange: dateChange("dateTo"),
998
+ onClear: () => onChange({ dateTo: void 0 }),
999
+ clearLabel: formatMessage({
1000
+ id: index.getTranslation("filters.clearDate"),
1001
+ defaultMessage: "Clear date"
1002
+ })
1003
+ }
1004
+ )
1005
+ ] }) })
1006
+ ] }),
1007
+ hasActiveFilter ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { paddingTop: 4, justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, {}), onClick: onClear, children: formatMessage({
1008
+ id: index.getTranslation("filters.clear"),
1009
+ defaultMessage: "Clear all filters"
1010
+ }) }) }) : null
1011
+ ] });
1012
+ };
1013
+ const COLUMNS = [
1014
+ { key: "createdAt", label: "Date", sortable: true },
1015
+ { key: "userName", label: "User", sortable: true },
1016
+ { key: "action", label: "Action", sortable: true },
1017
+ { key: "contentType", label: "Content type", sortable: true },
1018
+ { key: "contentDocumentId", label: "Document ID", sortable: false },
1019
+ { key: "locale", label: "Locale", sortable: true },
1020
+ { key: "source", label: "Source", sortable: true },
1021
+ { key: "outcome", label: "Outcome", sortable: true }
1022
+ ];
1023
+ const SortIndicator = ({ direction }) => {
1024
+ if (!direction) return null;
1025
+ return direction === "asc" ? /* @__PURE__ */ jsxRuntime.jsx(icons.CaretUp, {}) : /* @__PURE__ */ jsxRuntime.jsx(icons.CaretDown, {});
1026
+ };
1027
+ const AuditLogTable = ({
1028
+ logs,
1029
+ sort,
1030
+ onSortChange,
1031
+ onView,
1032
+ onDelete,
1033
+ isLoading
1034
+ }) => {
1035
+ const { formatMessage, locale } = reactIntl.useIntl();
1036
+ const [sortField, sortDirection] = React__namespace.useMemo(() => {
1037
+ const [field, direction] = (sort || "createdAt:desc").split(":");
1038
+ return [field, direction === "asc" ? "asc" : "desc"];
1039
+ }, [sort]);
1040
+ const toggleSort = (key) => {
1041
+ const direction = sortField === key && sortDirection === "desc" ? "asc" : "desc";
1042
+ onSortChange(`${key}:${direction}`);
1043
+ };
1044
+ if (!isLoading && logs.length === 0) {
1045
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
1046
+ designSystem.EmptyStateLayout,
1047
+ {
1048
+ content: formatMessage({
1049
+ id: index.getTranslation("list.empty"),
1050
+ defaultMessage: "No audit logs match these filters. Records appear here as soon as content is created, updated, deleted, published or unpublished."
1051
+ })
1052
+ }
1053
+ ) });
1054
+ }
1055
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: COLUMNS.length + 1, rowCount: logs.length + 1, children: [
1056
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
1057
+ COLUMNS.map(({ key, label, sortable }) => /* @__PURE__ */ jsxRuntime.jsx(
1058
+ designSystem.Th,
1059
+ {
1060
+ action: sortable ? /* @__PURE__ */ jsxRuntime.jsx(SortIndicator, { direction: sortField === key ? sortDirection : null }) : void 0,
1061
+ children: sortable ? /* @__PURE__ */ jsxRuntime.jsx(
1062
+ designSystem.Typography,
1063
+ {
1064
+ variant: "sigma",
1065
+ tag: "button",
1066
+ onClick: () => toggleSort(key),
1067
+ style: { background: "none", border: 0, cursor: "pointer", padding: 0 },
1068
+ children: formatMessage({ id: index.getTranslation(`list.column.${key}`), defaultMessage: label })
1069
+ }
1070
+ ) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: formatMessage({ id: index.getTranslation(`list.column.${key}`), defaultMessage: label }) })
1071
+ },
1072
+ key
1073
+ )),
1074
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.VisuallyHidden, { children: formatMessage({ id: index.getTranslation("list.column.actions"), defaultMessage: "Actions" }) }) })
1075
+ ] }) }),
1076
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: logs.map((log) => {
1077
+ const colors = actionColor(log.action);
1078
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { onClick: () => onView(log), style: { cursor: "pointer" }, children: [
1079
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: formatDate(log.createdAt, locale) }) }),
1080
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: formatUser(log) }) }),
1081
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: colors.background, textColor: colors.text, children: log.action }) }),
1082
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", title: log.contentType, children: log.contentTypeDisplayName || shortContentTypeName(log.contentType) }) }),
1083
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: log.contentDocumentId ?? "-" }) }),
1084
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: log.locale ?? "-" }) }),
1085
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: log.source }) }),
1086
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: log.outcome === "failure" ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "danger100", textColor: "danger600", children: log.outcome }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: log.outcome ?? "success" }) }),
1087
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, justifyContent: "flex-end", onClick: (event) => event.stopPropagation(), children: [
1088
+ /* @__PURE__ */ jsxRuntime.jsx(
1089
+ designSystem.IconButton,
1090
+ {
1091
+ label: formatMessage({
1092
+ id: index.getTranslation("list.view"),
1093
+ defaultMessage: "View details"
1094
+ }),
1095
+ variant: "ghost",
1096
+ onClick: () => onView(log),
1097
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Eye, {})
1098
+ }
1099
+ ),
1100
+ onDelete ? /* @__PURE__ */ jsxRuntime.jsx(
1101
+ designSystem.IconButton,
1102
+ {
1103
+ label: formatMessage({
1104
+ id: index.getTranslation("list.delete"),
1105
+ defaultMessage: "Delete this record"
1106
+ }),
1107
+ variant: "ghost",
1108
+ onClick: () => onDelete(log),
1109
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1110
+ }
1111
+ ) : null
1112
+ ] }) })
1113
+ ] }, log.id);
1114
+ }) })
1115
+ ] });
1116
+ };
1117
+ const toSearchParams = (query) => {
1118
+ const params = new URLSearchParams();
1119
+ for (const [key, value] of Object.entries(query)) {
1120
+ if (value === void 0 || value === null || value === "") continue;
1121
+ params.set(key, String(value));
1122
+ }
1123
+ return params.toString();
1124
+ };
1125
+ const AuditLogs = () => {
1126
+ const { formatMessage } = reactIntl.useIntl();
1127
+ const navigate = reactRouterDom.useNavigate();
1128
+ const { toggleNotification } = admin.useNotification();
1129
+ const [{ query }, setQuery] = admin.useQueryParams({ page: 1, pageSize: 20 });
1130
+ const {
1131
+ allowedActions: { canDelete },
1132
+ isLoading: isLoadingPermissions
1133
+ } = admin.useRBAC(index.PERMISSIONS);
1134
+ const search = React__namespace.useMemo(() => toSearchParams(query), [query]);
1135
+ const { data, pagination, isLoading, error, refresh } = useAuditLogs(search);
1136
+ const { options } = useAuditFilterOptions();
1137
+ const deleteLog = useDeleteAuditLog();
1138
+ const [pendingDelete, setPendingDelete] = React__namespace.useState(null);
1139
+ const updateQuery = React__namespace.useCallback(
1140
+ (patch) => {
1141
+ setQuery({ ...patch, page: 1 });
1142
+ },
1143
+ [setQuery]
1144
+ );
1145
+ const clearFilters = React__namespace.useCallback(() => {
1146
+ setQuery(
1147
+ {
1148
+ action: void 0,
1149
+ contentType: void 0,
1150
+ userId: void 0,
1151
+ locale: void 0,
1152
+ source: void 0,
1153
+ outcome: void 0,
1154
+ contentDocumentId: void 0,
1155
+ dateFrom: void 0,
1156
+ dateTo: void 0,
1157
+ page: 1
1158
+ },
1159
+ "remove"
1160
+ );
1161
+ }, [setQuery]);
1162
+ const confirmDelete = React__namespace.useCallback(async () => {
1163
+ if (!pendingDelete) return;
1164
+ try {
1165
+ await deleteLog(pendingDelete.id);
1166
+ toggleNotification({
1167
+ type: "success",
1168
+ message: formatMessage({
1169
+ id: index.getTranslation("delete.success"),
1170
+ defaultMessage: "Audit record deleted."
1171
+ })
1172
+ });
1173
+ refresh();
1174
+ } catch {
1175
+ toggleNotification({
1176
+ type: "danger",
1177
+ message: formatMessage({
1178
+ id: index.getTranslation("delete.error"),
1179
+ defaultMessage: "Could not delete this audit record."
1180
+ })
1181
+ });
1182
+ } finally {
1183
+ setPendingDelete(null);
1184
+ }
1185
+ }, [deleteLog, formatMessage, pendingDelete, refresh, toggleNotification]);
1186
+ if (isLoadingPermissions) {
1187
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, {});
1188
+ }
1189
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
1190
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: formatMessage({ id: index.getTranslation("plugin.name"), defaultMessage: "Audit Logs" }) }),
1191
+ /* @__PURE__ */ jsxRuntime.jsx(
1192
+ admin.Layouts.Header,
1193
+ {
1194
+ title: formatMessage({ id: index.getTranslation("plugin.name"), defaultMessage: "Audit Logs" }),
1195
+ subtitle: formatMessage(
1196
+ {
1197
+ id: index.getTranslation("list.subtitle"),
1198
+ defaultMessage: "{total, plural, =0 {No records} one {# record} other {# records}} of content changes"
1199
+ },
1200
+ { total: pagination.total }
1201
+ )
1202
+ }
1203
+ ),
1204
+ /* @__PURE__ */ jsxRuntime.jsx(
1205
+ admin.Layouts.Action,
1206
+ {
1207
+ startActions: /* @__PURE__ */ jsxRuntime.jsx(
1208
+ admin.SearchInput,
1209
+ {
1210
+ label: formatMessage({
1211
+ id: index.getTranslation("list.search"),
1212
+ defaultMessage: "Search by document id, user, content type or request id"
1213
+ })
1214
+ }
1215
+ )
1216
+ }
1217
+ ),
1218
+ /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
1219
+ /* @__PURE__ */ jsxRuntime.jsx(
1220
+ AuditLogFilters,
1221
+ {
1222
+ options,
1223
+ query,
1224
+ onChange: updateQuery,
1225
+ onClear: clearFilters
1226
+ }
1227
+ ),
1228
+ error ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "danger100", padding: 4, hasRadius: true, marginBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "danger600", children: error }) }) : null,
1229
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, {}) : /* @__PURE__ */ jsxRuntime.jsx(
1230
+ AuditLogTable,
1231
+ {
1232
+ logs: data,
1233
+ isLoading,
1234
+ sort: String(query.sort ?? "createdAt:desc"),
1235
+ onSortChange: (sort) => setQuery({ sort, page: 1 }),
1236
+ onView: (log) => navigate(`${log.id}`),
1237
+ onDelete: canDelete ? setPendingDelete : null
1238
+ }
1239
+ ),
1240
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
1241
+ admin.Pagination.Root,
1242
+ {
1243
+ pageCount: pagination.pageCount,
1244
+ total: pagination.total,
1245
+ defaultPageSize: pagination.pageSize,
1246
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
1247
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Pagination.PageSize, { options: ["10", "20", "50", "100"] }),
1248
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Pagination.Links, {})
1249
+ ] })
1250
+ }
1251
+ ) })
1252
+ ] }),
1253
+ /* @__PURE__ */ jsxRuntime.jsx(
1254
+ designSystem.Dialog.Root,
1255
+ {
1256
+ open: pendingDelete !== null,
1257
+ onOpenChange: (open) => {
1258
+ if (!open) setPendingDelete(null);
1259
+ },
1260
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1261
+ admin.ConfirmDialog,
1262
+ {
1263
+ title: formatMessage({
1264
+ id: index.getTranslation("delete.title"),
1265
+ defaultMessage: "Delete audit record"
1266
+ }),
1267
+ onConfirm: confirmDelete,
1268
+ onCancel: () => setPendingDelete(null),
1269
+ children: formatMessage({
1270
+ id: index.getTranslation("delete.confirm"),
1271
+ defaultMessage: "Audit records are evidence of what happened to your content. Deleting one cannot be undone. Are you sure?"
1272
+ })
1273
+ }
1274
+ )
1275
+ }
1276
+ )
1277
+ ] });
1278
+ };
1279
+ const App = () => /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Protect, { permissions: index.PERMISSIONS.read, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
1280
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(AuditLogs, {}) }),
1281
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: ":id", element: /* @__PURE__ */ jsxRuntime.jsx(AuditLogDetails, {}) }),
1282
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, {}) })
1283
+ ] }) });
1284
+ exports.App = App;