strapi-cms-audit-log 1.1.0 → 1.2.1

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 (34) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +12 -12
  3. package/dist/admin/{App-BLt4lqEM.js → App-CmbG2k9E.js} +110 -100
  4. package/dist/admin/{App-ou5hy99f.mjs → App-jWvaMXIw.mjs} +113 -103
  5. package/dist/admin/{en-BMlJxq3g.js → en-Bs4tlklI.js} +11 -9
  6. package/dist/admin/{en-B0rPiE2W.mjs → en-C3Og1VyK.mjs} +11 -9
  7. package/dist/admin/{index-DdmY-p3Q.mjs → index-BIIMJgKU.mjs} +3 -5
  8. package/dist/admin/{index-BYi8OPTw.js → index-BsVOGSDf.js} +3 -5
  9. package/dist/admin/index.js +1 -1
  10. package/dist/admin/index.mjs +1 -1
  11. package/dist/admin/src/components/AuditLogTable.d.ts +1 -2
  12. package/dist/admin/src/components/IntegrityStatus.d.ts +13 -0
  13. package/dist/admin/src/hooks/useAuditLogs.d.ts +14 -3
  14. package/dist/admin/src/permissions.d.ts +0 -8
  15. package/dist/admin/src/types.d.ts +26 -2
  16. package/dist/server/index.js +321 -94
  17. package/dist/server/index.mjs +321 -94
  18. package/dist/server/src/config/index.d.ts +1 -0
  19. package/dist/server/src/constants.d.ts +24 -2
  20. package/dist/server/src/content-types/audit-log/index.d.ts +8 -0
  21. package/dist/server/src/content-types/audit-log/schema.d.ts +21 -0
  22. package/dist/server/src/content-types/index.d.ts +8 -0
  23. package/dist/server/src/controllers/audit-log.d.ts +5 -3
  24. package/dist/server/src/controllers/index.d.ts +4 -4
  25. package/dist/server/src/index.d.ts +36 -7
  26. package/dist/server/src/register.d.ts +7 -3
  27. package/dist/server/src/routes/admin.d.ts +8 -0
  28. package/dist/server/src/services/audit.d.ts +0 -1
  29. package/dist/server/src/services/correlation.d.ts +35 -0
  30. package/dist/server/src/services/immutability.d.ts +5 -5
  31. package/dist/server/src/services/index.d.ts +21 -1
  32. package/dist/server/src/services/integrity.d.ts +69 -0
  33. package/dist/server/src/types/index.d.ts +54 -4
  34. package/package.json +10 -6
package/CHANGELOG.md CHANGED
@@ -5,6 +5,84 @@ All notable changes to this project are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
6
  the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.1] — 2026-11-06
9
+
10
+ ### Fixed
11
+ - Building the list page's filter options no longer takes extra pooled
12
+ database connections. `readStoredOptions` ran its six `DISTINCT` queries on
13
+ raw `strapi.db.connection`, which from inside an open transaction takes a
14
+ second connection per query and can exhaust a small pool. The queries now run
15
+ in turn on a single transaction connection.
16
+
17
+ ### Changed
18
+ - Removed the upper bound on the supported Node.js version; `engines.node` is
19
+ now `>=18.0.0`.
20
+
21
+ ## [1.2.0] — 2026-09-11
22
+
23
+ Remediates every finding of the 10 September OWASP ASVS Level 2 (V7) assessment.
24
+ The audit log can no longer be pruned by anyone, can prove it has not been, and
25
+ carries a correlation id on every request-borne record.
26
+
27
+ ### Security
28
+
29
+ **F-2 — audit records were deletable (HIGH, V7.3.1 / 7.3.3)**
30
+ - The `DELETE /audit-log/logs/:id` route, its controller handler, the
31
+ `plugin::audit-log.delete` RBAC action and the per-row delete button are
32
+ **removed**. There is no permission an administrator could grant to bring
33
+ deletion back. Strapi drops the stale grants from the database at the first
34
+ boot after upgrading.
35
+ - `strapi.documents(uid).delete()` on the audit type now throws, alongside
36
+ update, publish, unpublish, discardDraft and clone.
37
+ - The retention purge — the one deletion that remains — **records itself** as
38
+ `retention.purge`, with the count, the cutoff and the configured window. It is
39
+ not gated by `securityEvents`; there is no configuration in which a purge
40
+ should be silent.
41
+ - **Hash chain.** Every record stores `hash` (SHA-256 over its content fields,
42
+ `createdAt` and `prevHash`) and `prevHash`. Editing, removing or inserting a
43
+ record is detectable, and the verifier says at which row. Writes are
44
+ serialised — an in-process queue plus a PostgreSQL transaction-scoped
45
+ advisory lock for multi-replica deployments — so concurrent records cannot
46
+ fork the chain. `createdAt` is now set by the plugin rather than the database
47
+ so it can be part of the hash.
48
+ - New `integrity` service with `verify()`, new `GET /audit-log/integrity` route
49
+ (behind `read`), and a verdict line at the top of the list page on every visit.
50
+ - Rows written before 1.2.0 have no hash. They are counted as `legacy` and never
51
+ backfilled — a hash computed today over a row written last week vouches for
52
+ nothing about last week.
53
+
54
+ **F-3 — no off-box copy (MEDIUM, V7.3.3)**
55
+ - The forwarded log line now includes `id`, `hash` and `prevHash`, so the
56
+ collector's copy carries the chain and a row that has vanished from the
57
+ database is still provably in the external log. The consuming project should
58
+ set `forwardToLogger: true`; the mechanism was already there, switched off.
59
+
60
+ **F-4 — correlation ids absent on ~90% of records (LOW, V7.1.x)**
61
+ - New `correlation` middleware, registered ahead of the access middleware from
62
+ the plugin's `register` lifecycle: honours an inbound `x-request-id` /
63
+ `x-correlation-id` / `x-amzn-trace-id` when it is shaped like an id, mints a
64
+ UUID otherwise, and echoes it back as `X-Request-Id`. Every record written
65
+ inside a request now has one. New `correlationId` option, default `true`.
66
+
67
+ **F-1 — failed logins not observed (HIGH, V7.2.1)**
68
+ - No defect found: the listener is correct and the assessment created no
69
+ records to observe. A test now drives the exact shape Strapi's login
70
+ controller produces — an un-awaited `emit` followed by a synchronous throw —
71
+ and asserts the row lands. The README gains a one-line production check.
72
+
73
+ ### Added
74
+
75
+ - `AuditMaintenanceAction` (`'retention.purge'`), `IntegrityReport`, and the
76
+ `admin::audit-log` subject.
77
+ - `hash` and `prevHash` on `AuditLog`; both shown on the detail page.
78
+ - ASVS V7 mapping table in the README.
79
+
80
+ ### Removed
81
+
82
+ - `PERMISSIONS.delete`, `useDeleteAuditLog`, the `delete.*` translations, and
83
+ `audit.deleteOne()`. Consumers that referenced any of them will fail to compile,
84
+ which is the intended way to find out.
85
+
8
86
  ## [1.1.0] — 2026-09-03
9
87
 
10
88
  Adds the half of an audit trail that is not about content — authentication,
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # strapi-plugin-audit-log
1
+ # strapi-cms-audit-log
2
2
 
3
3
  Automatic audit logging for **Strapi v5**, in two halves.
4
4
 
@@ -103,11 +103,11 @@ Mapped against the usual checklist:
103
103
  ## Installation
104
104
 
105
105
  ```bash
106
- npm install strapi-plugin-audit-log
106
+ npm install strapi-cms-audit-log
107
107
  ```
108
108
 
109
109
  ```bash
110
- yarn add strapi-plugin-audit-log
110
+ yarn add strapi-cms-audit-log
111
111
  ```
112
112
 
113
113
  Then enable it in `config/plugins.ts`:
@@ -126,7 +126,7 @@ sidebar entry are all created on boot.
126
126
  | | |
127
127
  |---|---|
128
128
  | Strapi | `^5.0.0` (developed and tested against 5.52) |
129
- | Node | `>=18 <=22` |
129
+ | Node | `>=18` |
130
130
  | Database | Any Strapi v5 supports — PostgreSQL, MySQL/MariaDB, SQLite |
131
131
 
132
132
  There is one manual step after installing: **grant the permission**. See
@@ -503,7 +503,7 @@ Worth alerting on: repeated `login.failed` from one `ipAddress`, any
503
503
  Developing the plugin alongside a Strapi app usually means a path dependency:
504
504
 
505
505
  ```json
506
- { "dependencies": { "strapi-plugin-audit-log": "file:../strapi-plugin-audit-log" } }
506
+ { "dependencies": { "strapi-cms-audit-log": "file:../strapi-cms-audit-log" } }
507
507
  ```
508
508
 
509
509
  That works, with **one thing you must clean up afterwards**.
@@ -511,7 +511,7 @@ That works, with **one thing you must clean up afterwards**.
511
511
  Yarn 1's `file:` protocol copies the directory wholesale. It does not honour the
512
512
  `files` field and it does not skip `node_modules`, so the plugin's *development*
513
513
  dependencies are installed into your app at
514
- `node_modules/strapi-plugin-audit-log/node_modules/` — `@strapi/strapi`,
514
+ `node_modules/strapi-cms-audit-log/node_modules/` — `@strapi/strapi`,
515
515
  `@strapi/admin`, `react`, `react-dom`, `react-router-dom`, `react-intl` and
516
516
  `styled-components` among them.
517
517
 
@@ -539,13 +539,13 @@ nothing wrong to find in any of them.
539
539
  npm would have given you anyway:
540
540
 
541
541
  ```bash
542
- rm -rf node_modules/strapi-plugin-audit-log/node_modules
542
+ rm -rf node_modules/strapi-cms-audit-log/node_modules
543
543
  ```
544
544
 
545
545
  Make it survive the next install with a `postinstall` script:
546
546
 
547
547
  ```json
548
- { "scripts": { "postinstall": "rm -rf node_modules/strapi-plugin-audit-log/node_modules" } }
548
+ { "scripts": { "postinstall": "rm -rf node_modules/strapi-cms-audit-log/node_modules" } }
549
549
  ```
550
550
 
551
551
  Then clear Vite's dependency cache (`rm -rf node_modules/.strapi/vite`), restart
@@ -555,7 +555,7 @@ Two more things about a path install, unrelated to the above:
555
555
 
556
556
  - Yarn **copies** rather than links, so changes to the plugin need
557
557
  `npm run build` in the plugin followed by re-copying `dist/` into
558
- `node_modules/strapi-plugin-audit-log/dist`. A bare `yarn install` will not
558
+ `node_modules/strapi-cms-audit-log/dist`. A bare `yarn install` will not
559
559
  refresh it — the resolution is unchanged, so yarn skips the copy.
560
560
  - Strapi loads plugins at boot, so a restart is always required.
561
561
 
@@ -748,7 +748,7 @@ trusting a spoofable header.
748
748
  ### Why admin and server types are separate
749
749
 
750
750
  The canonical types live in `server/src/types` and are re-exported from
751
- `strapi-plugin-audit-log/strapi-server`. The admin panel keeps its own copies of
751
+ `strapi-cms-audit-log/strapi-server`. The admin panel keeps its own copies of
752
752
  the few shared unions in `admin/src/types.ts`.
753
753
 
754
754
  That is a build constraint, not an oversight. `@strapi/sdk-plugin` compiles the
@@ -941,7 +941,7 @@ import type {
941
941
  AuditUserConfig,
942
942
  AuditWriteMode,
943
943
  ContentTypeSelector,
944
- } from 'strapi-plugin-audit-log/strapi-server';
944
+ } from 'strapi-cms-audit-log/strapi-server';
945
945
  ```
946
946
 
947
947
  `PLUGIN_ID`, `AUDIT_LOG_UID`, `PERMISSIONS` and `DEFAULT_IGNORED_FIELDS` are
@@ -979,7 +979,7 @@ two-field edit on a 173-widget page does not populate 173 widgets.
979
979
  npm run watch:link
980
980
 
981
981
  # in your Strapi project
982
- npx yalc add --link strapi-plugin-audit-log && npm install
982
+ npx yalc add --link strapi-cms-audit-log && npm install
983
983
  npm run develop
984
984
  ```
985
985
 
@@ -3,7 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const admin = require("@strapi/strapi/admin");
5
5
  const reactRouterDom = require("react-router-dom");
6
- const index = require("./index-BYi8OPTw.js");
6
+ const index = require("./index-BsVOGSDf.js");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const reactIntl = require("react-intl");
9
9
  const React = require("react");
@@ -53,7 +53,9 @@ const ACTION_COLORS = {
53
53
  "media.delete": { background: "danger100", text: "danger600" },
54
54
  "media-folder.create": { background: "success100", text: "success600" },
55
55
  "media-folder.update": { background: "primary100", text: "primary600" },
56
- "media-folder.delete": { background: "danger100", text: "danger600" }
56
+ "media-folder.delete": { background: "danger100", text: "danger600" },
57
+ // The log's own housekeeping. Neutral: expected, scheduled, and self-reported.
58
+ "retention.purge": { background: "neutral150", text: "neutral700" }
57
59
  };
58
60
  const actionColor = (action) => ACTION_COLORS[action] ?? { background: "neutral150", text: "neutral700" };
59
61
  const shortContentTypeName = (uid) => {
@@ -588,6 +590,30 @@ const useAuditFilterOptions = () => {
588
590
  }, [get]);
589
591
  return { options, isLoading };
590
592
  };
593
+ const useAuditIntegrity = () => {
594
+ const { get } = admin.useFetchClient();
595
+ const [data, setData] = React__namespace.useState(null);
596
+ const [isLoading, setIsLoading] = React__namespace.useState(true);
597
+ const [error, setError] = React__namespace.useState(null);
598
+ React__namespace.useEffect(() => {
599
+ let cancelled = false;
600
+ const load = async () => {
601
+ try {
602
+ const { data: response } = await get(`/${index.PLUGIN_ID}/integrity`);
603
+ if (!cancelled) setData(response.data);
604
+ } catch (err) {
605
+ if (!cancelled) setError(message(err));
606
+ } finally {
607
+ if (!cancelled) setIsLoading(false);
608
+ }
609
+ };
610
+ void load();
611
+ return () => {
612
+ cancelled = true;
613
+ };
614
+ }, [get]);
615
+ return { data, isLoading, error };
616
+ };
591
617
  const useAuditLog = (id) => {
592
618
  const { get } = admin.useFetchClient();
593
619
  const [data, setData] = React__namespace.useState(null);
@@ -622,15 +648,6 @@ const useAuditLog = (id) => {
622
648
  }, [get, id]);
623
649
  return { data, isLoading, error };
624
650
  };
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
651
  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
652
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: label }),
636
653
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -806,6 +823,25 @@ const AuditLogDetails = () => {
806
823
  value: log.userAgent,
807
824
  mono: true
808
825
  }
826
+ ),
827
+ /* @__PURE__ */ jsxRuntime.jsx(
828
+ MetaField,
829
+ {
830
+ label: formatMessage({ id: index.getTranslation("detail.hash"), defaultMessage: "Hash" }),
831
+ value: log.hash,
832
+ mono: true
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsxRuntime.jsx(
836
+ MetaField,
837
+ {
838
+ label: formatMessage({
839
+ id: index.getTranslation("detail.prevHash"),
840
+ defaultMessage: "Previous hash"
841
+ }),
842
+ value: log.prevHash,
843
+ mono: true
844
+ }
809
845
  )
810
846
  ] }) })
811
847
  ] }),
@@ -1029,7 +1065,6 @@ const AuditLogTable = ({
1029
1065
  sort,
1030
1066
  onSortChange,
1031
1067
  onView,
1032
- onDelete,
1033
1068
  isLoading
1034
1069
  }) => {
1035
1070
  const { formatMessage, locale } = reactIntl.useIntl();
@@ -1084,36 +1119,69 @@ const AuditLogTable = ({
1084
1119
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", children: log.locale ?? "-" }) }),
1085
1120
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: log.source }) }),
1086
1121
  /* @__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
- ] }) })
1122
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 1, justifyContent: "flex-end", onClick: (event) => event.stopPropagation(), children: /* @__PURE__ */ jsxRuntime.jsx(
1123
+ designSystem.IconButton,
1124
+ {
1125
+ label: formatMessage({
1126
+ id: index.getTranslation("list.view"),
1127
+ defaultMessage: "View details"
1128
+ }),
1129
+ variant: "ghost",
1130
+ onClick: () => onView(log),
1131
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Eye, {})
1132
+ }
1133
+ ) }) })
1113
1134
  ] }, log.id);
1114
1135
  }) })
1115
1136
  ] });
1116
1137
  };
1138
+ const IntegrityStatus = () => {
1139
+ const { formatMessage, locale } = reactIntl.useIntl();
1140
+ const { data, isLoading, error } = useAuditIntegrity();
1141
+ if (isLoading) {
1142
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
1143
+ id: index.getTranslation("integrity.checking"),
1144
+ defaultMessage: "Verifying the audit chain…"
1145
+ }) });
1146
+ }
1147
+ if (error || !data) {
1148
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "warning600", children: formatMessage(
1149
+ {
1150
+ id: index.getTranslation("integrity.unavailable"),
1151
+ defaultMessage: "Integrity check unavailable: {error}"
1152
+ },
1153
+ { error: error ?? "no report" }
1154
+ ) });
1155
+ }
1156
+ if (!data.ok) {
1157
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "danger100", hasRadius: true, padding: 3, borderColor: "danger200", borderWidth: "1px", borderStyle: "solid", children: [
1158
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, wrap: "wrap", children: [
1159
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "danger600", textColor: "neutral0", children: formatMessage({ id: index.getTranslation("integrity.broken"), defaultMessage: "CHAIN BROKEN" }) }),
1160
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger700", fontWeight: "bold", children: formatMessage(
1161
+ {
1162
+ id: index.getTranslation("integrity.brokenAt"),
1163
+ defaultMessage: "Record #{id}: {reason}"
1164
+ },
1165
+ { id: data.brokenAt?.id ?? "?", reason: data.brokenAt?.reason ?? "" }
1166
+ ) })
1167
+ ] }),
1168
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger700", children: formatMessage({
1169
+ id: index.getTranslation("integrity.brokenHint"),
1170
+ defaultMessage: "A record was modified, removed or inserted after it was written. Treat everything from this point as unverified and compare against the forwarded copy."
1171
+ }) }) })
1172
+ ] });
1173
+ }
1174
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, wrap: "wrap", children: [
1175
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "success100", textColor: "success700", children: formatMessage({ id: index.getTranslation("integrity.ok"), defaultMessage: "Chain verified" }) }),
1176
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage(
1177
+ {
1178
+ id: index.getTranslation("integrity.summary"),
1179
+ defaultMessage: "{hashed, plural, one {# linked record} other {# linked records}}{legacy, plural, =0 {} one { · # pre-chain record} other { · # pre-chain records}} · checked {when}"
1180
+ },
1181
+ { hashed: data.hashed, legacy: data.legacy, when: formatDate(data.verifiedAt, locale) }
1182
+ ) })
1183
+ ] });
1184
+ };
1117
1185
  const toSearchParams = (query) => {
1118
1186
  const params = new URLSearchParams();
1119
1187
  for (const [key, value] of Object.entries(query)) {
@@ -1125,17 +1193,10 @@ const toSearchParams = (query) => {
1125
1193
  const AuditLogs = () => {
1126
1194
  const { formatMessage } = reactIntl.useIntl();
1127
1195
  const navigate = reactRouterDom.useNavigate();
1128
- const { toggleNotification } = admin.useNotification();
1129
1196
  const [{ query }, setQuery] = admin.useQueryParams({ page: 1, pageSize: 20 });
1130
- const {
1131
- allowedActions: { canDelete },
1132
- isLoading: isLoadingPermissions
1133
- } = admin.useRBAC(index.PERMISSIONS);
1134
1197
  const search = React__namespace.useMemo(() => toSearchParams(query), [query]);
1135
- const { data, pagination, isLoading, error, refresh } = useAuditLogs(search);
1198
+ const { data, pagination, isLoading, error } = useAuditLogs(search);
1136
1199
  const { options } = useAuditFilterOptions();
1137
- const deleteLog = useDeleteAuditLog();
1138
- const [pendingDelete, setPendingDelete] = React__namespace.useState(null);
1139
1200
  const updateQuery = React__namespace.useCallback(
1140
1201
  (patch) => {
1141
1202
  setQuery({ ...patch, page: 1 });
@@ -1159,33 +1220,6 @@ const AuditLogs = () => {
1159
1220
  "remove"
1160
1221
  );
1161
1222
  }, [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
1223
  return /* @__PURE__ */ jsxRuntime.jsxs(admin.Page.Main, { children: [
1190
1224
  /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Title, { children: formatMessage({ id: index.getTranslation("plugin.name"), defaultMessage: "Audit Logs" }) }),
1191
1225
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1216,6 +1250,7 @@ const AuditLogs = () => {
1216
1250
  }
1217
1251
  ),
1218
1252
  /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
1253
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(IntegrityStatus, {}) }),
1219
1254
  /* @__PURE__ */ jsxRuntime.jsx(
1220
1255
  AuditLogFilters,
1221
1256
  {
@@ -1233,8 +1268,7 @@ const AuditLogs = () => {
1233
1268
  isLoading,
1234
1269
  sort: String(query.sort ?? "createdAt:desc"),
1235
1270
  onSortChange: (sort) => setQuery({ sort, page: 1 }),
1236
- onView: (log) => navigate(`${log.id}`),
1237
- onDelete: canDelete ? setPendingDelete : null
1271
+ onView: (log) => navigate(`${log.id}`)
1238
1272
  }
1239
1273
  ),
1240
1274
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -1249,31 +1283,7 @@ const AuditLogs = () => {
1249
1283
  ] })
1250
1284
  }
1251
1285
  ) })
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
- )
1286
+ ] })
1277
1287
  ] });
1278
1288
  };
1279
1289
  const App = () => /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Protect, { permissions: index.PERMISSIONS.read, children: /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [