@adobedjangir/commerce-admin-audit-log 0.1.0 → 0.1.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.
- package/package.json +24 -5
- package/scripts/build-web.js +39 -0
- package/scripts/setup.js +4 -1
- package/web/dist/index.js +471 -0
package/package.json
CHANGED
|
@@ -1,21 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobedjangir/commerce-admin-audit-log",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Audit log + revert add-on for @adobedjangir/commerce-admin-management. Records every system_config change with old/new/actor and exposes a UI tab to inspect + revert.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Adobe Inc.",
|
|
7
|
-
"keywords": [
|
|
7
|
+
"keywords": [
|
|
8
|
+
"adobe-io",
|
|
9
|
+
"aio",
|
|
10
|
+
"app-builder",
|
|
11
|
+
"commerce-admin",
|
|
12
|
+
"audit-log"
|
|
13
|
+
],
|
|
8
14
|
"main": "./src/index.js",
|
|
9
15
|
"exports": {
|
|
10
16
|
".": "./src/index.js",
|
|
11
17
|
"./hook": "./src/hook.js",
|
|
12
|
-
"./web": "./web/index.js"
|
|
18
|
+
"./web": "./web/dist/index.js"
|
|
13
19
|
},
|
|
14
20
|
"scripts": {
|
|
21
|
+
"build": "node ./scripts/build-web.js",
|
|
22
|
+
"prepublishOnly": "node ./scripts/build-web.js",
|
|
15
23
|
"postinstall": "node ./scripts/setup.js"
|
|
16
24
|
},
|
|
17
|
-
"files": [
|
|
18
|
-
|
|
25
|
+
"files": [
|
|
26
|
+
"src",
|
|
27
|
+
"actions",
|
|
28
|
+
"web",
|
|
29
|
+
"scripts",
|
|
30
|
+
"README.md"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"esbuild": "^0.25.0"
|
|
37
|
+
},
|
|
19
38
|
"peerDependencies": {
|
|
20
39
|
"@adobedjangir/commerce-admin-management": ">=0.3.0",
|
|
21
40
|
"@adobedjangir/commerce-admin-get-config": ">=0.0.7"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
Copyright 2025 Adobe. All rights reserved.
|
|
4
|
+
Licensed under the Apache License, Version 2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Pre-bundle this add-on's web entry so the host's Parcel build can
|
|
8
|
+
// consume a single JS file without having to transform JSX from
|
|
9
|
+
// node_modules. Mirrors core's build-web.js.
|
|
10
|
+
|
|
11
|
+
const fs = require('fs')
|
|
12
|
+
const path = require('path')
|
|
13
|
+
|
|
14
|
+
async function main () {
|
|
15
|
+
let esbuild
|
|
16
|
+
try { esbuild = require('esbuild') } catch {
|
|
17
|
+
console.error('[@adobedjangir/commerce-admin-audit-log] esbuild missing — run `npm install` in the package directory.')
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
const pkgRoot = path.join(__dirname, '..')
|
|
21
|
+
const entry = path.join(pkgRoot, 'web', 'index.js')
|
|
22
|
+
const outdir = path.join(pkgRoot, 'web', 'dist')
|
|
23
|
+
fs.mkdirSync(outdir, { recursive: true })
|
|
24
|
+
await esbuild.build({
|
|
25
|
+
entryPoints: [entry],
|
|
26
|
+
bundle: true,
|
|
27
|
+
format: 'esm',
|
|
28
|
+
platform: 'browser',
|
|
29
|
+
outfile: path.join(outdir, 'index.js'),
|
|
30
|
+
packages: 'external',
|
|
31
|
+
jsx: 'automatic',
|
|
32
|
+
loader: { '.js': 'jsx', '.css': 'css' },
|
|
33
|
+
target: ['chrome79', 'firefox85', 'safari13'],
|
|
34
|
+
logLevel: 'info'
|
|
35
|
+
})
|
|
36
|
+
console.log('[@adobedjangir/commerce-admin-audit-log] built web/dist/index.js')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (require.main === module) main().catch((e) => { console.error(e.message); process.exit(1) })
|
package/scripts/setup.js
CHANGED
|
@@ -35,8 +35,11 @@ function resolveProjectRoot () {
|
|
|
35
35
|
*/
|
|
36
36
|
function patchAppConfig (content) {
|
|
37
37
|
// Look for our own marker to detect prior installs.
|
|
38
|
+
// Escape marker special chars before interpolating into the regex —
|
|
39
|
+
// `(auto-linked on npm install)` previously broke the detection.
|
|
40
|
+
const ESC = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
38
41
|
const blockRe = new RegExp(
|
|
39
|
-
|
|
42
|
+
"^[ \\t]*" + ESC(MARKER) + "\\n[ \\t]+" + RUNTIME_KEY + ":[ \\t]*\\n[ \\t]+\\$include:[^\\n]*\\n", "m"
|
|
40
43
|
)
|
|
41
44
|
const desiredBody = ` ${MARKER}\n ${RUNTIME_KEY}:\n $include: ${INCLUDE_REL}\n`
|
|
42
45
|
|
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
// web/index.js
|
|
2
|
+
import { configureWeb } from "@adobedjangir/commerce-admin-management/web";
|
|
3
|
+
|
|
4
|
+
// web/src/AuditLog.js
|
|
5
|
+
import React, { useCallback, useEffect, useState } from "react";
|
|
6
|
+
import {
|
|
7
|
+
View,
|
|
8
|
+
Flex,
|
|
9
|
+
Heading,
|
|
10
|
+
Text,
|
|
11
|
+
TextField,
|
|
12
|
+
Picker,
|
|
13
|
+
Item,
|
|
14
|
+
Button,
|
|
15
|
+
SearchField,
|
|
16
|
+
ProgressCircle,
|
|
17
|
+
Well,
|
|
18
|
+
StatusLight,
|
|
19
|
+
DialogTrigger,
|
|
20
|
+
Dialog,
|
|
21
|
+
Content,
|
|
22
|
+
Header,
|
|
23
|
+
Divider,
|
|
24
|
+
ButtonGroup
|
|
25
|
+
} from "@adobe/react-spectrum";
|
|
26
|
+
import { callAction, resolveActor } from "@adobedjangir/commerce-admin-management/web";
|
|
27
|
+
import { getActionKey } from "@adobedjangir/commerce-admin-management/web";
|
|
28
|
+
import { PALETTE, RADIUS, SHADOW } from "@adobedjangir/commerce-admin-management/web";
|
|
29
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
30
|
+
var PAGE_SIZE_OPTIONS = [
|
|
31
|
+
{ id: "25", label: "25 / page" },
|
|
32
|
+
{ id: "50", label: "50 / page" },
|
|
33
|
+
{ id: "100", label: "100 / page" },
|
|
34
|
+
{ id: "200", label: "200 / page" }
|
|
35
|
+
];
|
|
36
|
+
var DEFAULT_PAGE_SIZE = 50;
|
|
37
|
+
var SENSITIVE_TOKEN = "[ENCRYPTED]";
|
|
38
|
+
var USE_DEFAULT_SENTINEL = "__USE_DEFAULT__";
|
|
39
|
+
var ACTION_OPTIONS = [
|
|
40
|
+
{ id: "any", label: "Any action" },
|
|
41
|
+
{ id: "create", label: "Create" },
|
|
42
|
+
{ id: "update", label: "Update" },
|
|
43
|
+
{ id: "delete", label: "Delete" }
|
|
44
|
+
];
|
|
45
|
+
var SCOPE_OPTIONS = [
|
|
46
|
+
{ id: "any", label: "Any scope" },
|
|
47
|
+
{ id: "default", label: "Default" },
|
|
48
|
+
{ id: "websites", label: "Websites" },
|
|
49
|
+
{ id: "stores", label: "Stores" }
|
|
50
|
+
];
|
|
51
|
+
function actionTone(a) {
|
|
52
|
+
if (a === "create") return "positive";
|
|
53
|
+
if (a === "delete") return "negative";
|
|
54
|
+
if (a === "update") return "notice";
|
|
55
|
+
return "neutral";
|
|
56
|
+
}
|
|
57
|
+
function fmtTime(iso) {
|
|
58
|
+
if (!iso) return "";
|
|
59
|
+
try {
|
|
60
|
+
return new Date(iso).toLocaleString();
|
|
61
|
+
} catch (_) {
|
|
62
|
+
return iso;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function fmtValue(v) {
|
|
66
|
+
if (v == null) return "\u2205";
|
|
67
|
+
if (typeof v === "string") return v;
|
|
68
|
+
try {
|
|
69
|
+
return JSON.stringify(v);
|
|
70
|
+
} catch (_) {
|
|
71
|
+
return String(v);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function buildRevertPayload(row) {
|
|
75
|
+
if (!row || !row.path) {
|
|
76
|
+
return { ok: false, message: "Audit row is malformed." };
|
|
77
|
+
}
|
|
78
|
+
if (row.oldValue === SENSITIVE_TOKEN || row.newValue === SENSITIVE_TOKEN) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
message: "This change is for a sensitive (encrypted) field \u2014 the original value is not stored in the audit log, so it cannot be reverted from here."
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (row.action === "create" || row.oldValue == null) {
|
|
85
|
+
return {
|
|
86
|
+
ok: true,
|
|
87
|
+
payload: {
|
|
88
|
+
values: { [row.path]: USE_DEFAULT_SENTINEL },
|
|
89
|
+
scope: row.scope || "default",
|
|
90
|
+
scopeId: row.scope_id || "0"
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
ok: true,
|
|
96
|
+
payload: {
|
|
97
|
+
values: { [row.path]: row.oldValue },
|
|
98
|
+
scope: row.scope || "default",
|
|
99
|
+
scopeId: row.scope_id || "0"
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function AuditLog({ runtime, ims }) {
|
|
104
|
+
const [items, setItems] = useState([]);
|
|
105
|
+
const [loading, setLoading] = useState(false);
|
|
106
|
+
const [error, setError] = useState(null);
|
|
107
|
+
const [page, setPage] = useState(0);
|
|
108
|
+
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
|
109
|
+
const [returned, setReturned] = useState(0);
|
|
110
|
+
const [status, setStatus] = useState({ tone: "neutral", message: "" });
|
|
111
|
+
const [confirmRow, setConfirmRow] = useState(null);
|
|
112
|
+
const [reverting, setReverting] = useState(false);
|
|
113
|
+
const [scope, setScope] = useState("any");
|
|
114
|
+
const [actionFilter, setActionFilter] = useState("any");
|
|
115
|
+
const [pathFilter, setPathFilter] = useState("");
|
|
116
|
+
const [actor, setActor] = useState("");
|
|
117
|
+
const [since, setSince] = useState("");
|
|
118
|
+
const [until, setUntil] = useState("");
|
|
119
|
+
const fetchPage = useCallback(async (nextPage = 0, sizeOverride = null) => {
|
|
120
|
+
var _a;
|
|
121
|
+
const size = sizeOverride || pageSize;
|
|
122
|
+
setLoading(true);
|
|
123
|
+
setError(null);
|
|
124
|
+
try {
|
|
125
|
+
const params = { limit: size, skip: nextPage * size };
|
|
126
|
+
if (scope !== "any") params.scope = scope;
|
|
127
|
+
if (actionFilter !== "any") params.action = actionFilter;
|
|
128
|
+
if (pathFilter.trim()) params.path = pathFilter.trim();
|
|
129
|
+
if (actor.trim()) params.actor = actor.trim();
|
|
130
|
+
if (since.trim()) params.since = since.trim();
|
|
131
|
+
if (until.trim()) params.until = until.trim();
|
|
132
|
+
const res = await callAction(
|
|
133
|
+
{ runtime, ims },
|
|
134
|
+
getActionKey("systemConfigAuditList"),
|
|
135
|
+
"",
|
|
136
|
+
params
|
|
137
|
+
);
|
|
138
|
+
const body = (res == null ? void 0 : res.body) || res;
|
|
139
|
+
const next = Array.isArray(body == null ? void 0 : body.items) ? body.items : [];
|
|
140
|
+
setItems(next);
|
|
141
|
+
setReturned((_a = body == null ? void 0 : body.returned) != null ? _a : next.length);
|
|
142
|
+
setPage(nextPage);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
setError(e.message || "Failed to load audit log");
|
|
145
|
+
} finally {
|
|
146
|
+
setLoading(false);
|
|
147
|
+
}
|
|
148
|
+
}, [runtime, ims, scope, actionFilter, pathFilter, actor, since, until, pageSize]);
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
fetchPage(0);
|
|
151
|
+
}, []);
|
|
152
|
+
const onSearch = () => fetchPage(0);
|
|
153
|
+
const onPrev = () => {
|
|
154
|
+
if (page > 0) fetchPage(page - 1);
|
|
155
|
+
};
|
|
156
|
+
const onNext = () => {
|
|
157
|
+
if (returned >= pageSize) fetchPage(page + 1);
|
|
158
|
+
};
|
|
159
|
+
const onChangePageSize = (next) => {
|
|
160
|
+
const n = Number(next) || DEFAULT_PAGE_SIZE;
|
|
161
|
+
setPageSize(n);
|
|
162
|
+
fetchPage(0, n);
|
|
163
|
+
};
|
|
164
|
+
const hasNext = returned >= pageSize;
|
|
165
|
+
const hasPrev = page > 0;
|
|
166
|
+
const Pagination = () => /* @__PURE__ */ jsx(
|
|
167
|
+
View,
|
|
168
|
+
{
|
|
169
|
+
paddingX: "size-200",
|
|
170
|
+
paddingY: "size-150",
|
|
171
|
+
UNSAFE_style: {
|
|
172
|
+
background: PALETTE.surfaceMuted,
|
|
173
|
+
borderBottom: `1px solid ${PALETTE.border}`
|
|
174
|
+
},
|
|
175
|
+
children: /* @__PURE__ */ jsxs(Flex, { gap: "size-200", alignItems: "center", justifyContent: "space-between", wrap: true, children: [
|
|
176
|
+
/* @__PURE__ */ jsxs(Flex, { gap: "size-150", alignItems: "center", wrap: true, children: [
|
|
177
|
+
/* @__PURE__ */ jsx(
|
|
178
|
+
Picker,
|
|
179
|
+
{
|
|
180
|
+
"aria-label": "Rows per page",
|
|
181
|
+
selectedKey: String(pageSize),
|
|
182
|
+
onSelectionChange: onChangePageSize,
|
|
183
|
+
width: "size-1700",
|
|
184
|
+
isDisabled: loading,
|
|
185
|
+
children: PAGE_SIZE_OPTIONS.map((o) => /* @__PURE__ */ jsx(Item, { children: o.label }, o.id))
|
|
186
|
+
}
|
|
187
|
+
),
|
|
188
|
+
/* @__PURE__ */ jsx(Text, { UNSAFE_style: { color: PALETTE.textMuted, fontSize: 12 }, children: items.length === 0 ? "No rows" : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
189
|
+
"Page ",
|
|
190
|
+
/* @__PURE__ */ jsx("strong", { children: page + 1 }),
|
|
191
|
+
" \xB7 showing rows ",
|
|
192
|
+
page * pageSize + 1,
|
|
193
|
+
"\u2013",
|
|
194
|
+
page * pageSize + returned
|
|
195
|
+
] }) })
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsxs(Flex, { gap: "size-100", children: [
|
|
198
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onPress: onPrev, isDisabled: !hasPrev || loading, children: "\u2190 Prev" }),
|
|
199
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onPress: onNext, isDisabled: !hasNext || loading, children: "Next \u2192" })
|
|
200
|
+
] })
|
|
201
|
+
] })
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
const doRevert = useCallback(async (row) => {
|
|
205
|
+
const built = buildRevertPayload(row);
|
|
206
|
+
if (!built.ok) {
|
|
207
|
+
setStatus({ tone: "negative", message: built.message });
|
|
208
|
+
setConfirmRow(null);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const baseActor = resolveActor(ims);
|
|
212
|
+
const tagged = row.changedAt ? `${baseActor} (revert of ${row.changedAt})` : baseActor;
|
|
213
|
+
setReverting(true);
|
|
214
|
+
setStatus({ tone: "notice", message: "Reverting\u2026" });
|
|
215
|
+
try {
|
|
216
|
+
const res = await callAction(
|
|
217
|
+
{ runtime, ims },
|
|
218
|
+
getActionKey("systemConfigSave"),
|
|
219
|
+
"",
|
|
220
|
+
{ ...built.payload, actor: tagged }
|
|
221
|
+
);
|
|
222
|
+
const body = (res == null ? void 0 : res.body) || res;
|
|
223
|
+
if (body && body.fieldErrors) {
|
|
224
|
+
const first = Object.values(body.fieldErrors)[0];
|
|
225
|
+
setStatus({ tone: "negative", message: first || "Revert rejected by validation" });
|
|
226
|
+
} else {
|
|
227
|
+
setStatus({
|
|
228
|
+
tone: "positive",
|
|
229
|
+
message: `Reverted ${row.path} at ${row.scope}:${row.scope_id}`
|
|
230
|
+
});
|
|
231
|
+
await fetchPage(0);
|
|
232
|
+
}
|
|
233
|
+
} catch (e) {
|
|
234
|
+
setStatus({ tone: "negative", message: e.message || "Revert failed" });
|
|
235
|
+
} finally {
|
|
236
|
+
setReverting(false);
|
|
237
|
+
setConfirmRow(null);
|
|
238
|
+
}
|
|
239
|
+
}, [runtime, ims, fetchPage]);
|
|
240
|
+
const startRevert = (row) => {
|
|
241
|
+
const built = buildRevertPayload(row);
|
|
242
|
+
if (!built.ok) {
|
|
243
|
+
setStatus({ tone: "negative", message: built.message });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
setStatus({ tone: "neutral", message: "" });
|
|
247
|
+
setConfirmRow(row);
|
|
248
|
+
};
|
|
249
|
+
return /* @__PURE__ */ jsxs(View, { padding: "size-400", UNSAFE_style: { background: PALETTE.bg, minHeight: "100vh" }, children: [
|
|
250
|
+
/* @__PURE__ */ jsx(Heading, { level: 2, marginTop: 0, children: "Audit Log" }),
|
|
251
|
+
/* @__PURE__ */ jsxs(Text, { UNSAFE_style: { color: PALETTE.textMuted }, children: [
|
|
252
|
+
"Every save to system_config_data is recorded here with old \u2192 new values. Sensitive fields show ",
|
|
253
|
+
/* @__PURE__ */ jsx("code", { children: "[ENCRYPTED]" }),
|
|
254
|
+
" and cannot be reverted from here. Newest entries first."
|
|
255
|
+
] }),
|
|
256
|
+
status.message && /* @__PURE__ */ jsx(View, { marginTop: "size-150", children: /* @__PURE__ */ jsx(StatusLight, { variant: status.tone, children: status.message }) }),
|
|
257
|
+
/* @__PURE__ */ jsx(
|
|
258
|
+
View,
|
|
259
|
+
{
|
|
260
|
+
marginTop: "size-200",
|
|
261
|
+
padding: "size-200",
|
|
262
|
+
UNSAFE_style: {
|
|
263
|
+
background: PALETTE.surface,
|
|
264
|
+
border: `1px solid ${PALETTE.border}`,
|
|
265
|
+
borderRadius: RADIUS.lg,
|
|
266
|
+
boxShadow: SHADOW.xs
|
|
267
|
+
},
|
|
268
|
+
children: /* @__PURE__ */ jsxs(Flex, { gap: "size-150", wrap: true, alignItems: "end", children: [
|
|
269
|
+
/* @__PURE__ */ jsx(Picker, { label: "Scope", selectedKey: scope, onSelectionChange: setScope, width: "size-1700", children: SCOPE_OPTIONS.map((s) => /* @__PURE__ */ jsx(Item, { children: s.label }, s.id)) }),
|
|
270
|
+
/* @__PURE__ */ jsx(Picker, { label: "Action", selectedKey: actionFilter, onSelectionChange: setActionFilter, width: "size-1700", children: ACTION_OPTIONS.map((s) => /* @__PURE__ */ jsx(Item, { children: s.label }, s.id)) }),
|
|
271
|
+
/* @__PURE__ */ jsx(
|
|
272
|
+
SearchField,
|
|
273
|
+
{
|
|
274
|
+
label: "Path contains",
|
|
275
|
+
value: pathFilter,
|
|
276
|
+
onChange: setPathFilter,
|
|
277
|
+
onSubmit: onSearch,
|
|
278
|
+
width: "size-2400",
|
|
279
|
+
placeholder: "section/group/field"
|
|
280
|
+
}
|
|
281
|
+
),
|
|
282
|
+
/* @__PURE__ */ jsx(
|
|
283
|
+
TextField,
|
|
284
|
+
{
|
|
285
|
+
label: "Actor",
|
|
286
|
+
value: actor,
|
|
287
|
+
onChange: setActor,
|
|
288
|
+
width: "size-2400",
|
|
289
|
+
placeholder: "email or org id"
|
|
290
|
+
}
|
|
291
|
+
),
|
|
292
|
+
/* @__PURE__ */ jsx(
|
|
293
|
+
TextField,
|
|
294
|
+
{
|
|
295
|
+
label: "Since (ISO)",
|
|
296
|
+
value: since,
|
|
297
|
+
onChange: setSince,
|
|
298
|
+
width: "size-2400",
|
|
299
|
+
placeholder: "2026-01-01T00:00:00Z"
|
|
300
|
+
}
|
|
301
|
+
),
|
|
302
|
+
/* @__PURE__ */ jsx(
|
|
303
|
+
TextField,
|
|
304
|
+
{
|
|
305
|
+
label: "Until (ISO)",
|
|
306
|
+
value: until,
|
|
307
|
+
onChange: setUntil,
|
|
308
|
+
width: "size-2400",
|
|
309
|
+
placeholder: "2026-12-31T23:59:59Z"
|
|
310
|
+
}
|
|
311
|
+
),
|
|
312
|
+
/* @__PURE__ */ jsx(Button, { variant: "cta", onPress: onSearch, isDisabled: loading, children: loading ? "Loading\u2026" : "Search" })
|
|
313
|
+
] })
|
|
314
|
+
}
|
|
315
|
+
),
|
|
316
|
+
error && /* @__PURE__ */ jsx(Well, { marginTop: "size-200", UNSAFE_style: { borderColor: PALETTE.danger }, children: /* @__PURE__ */ jsx(Text, { UNSAFE_style: { color: PALETTE.danger }, children: error }) }),
|
|
317
|
+
/* @__PURE__ */ jsxs(
|
|
318
|
+
View,
|
|
319
|
+
{
|
|
320
|
+
marginTop: "size-200",
|
|
321
|
+
UNSAFE_style: {
|
|
322
|
+
background: PALETTE.surface,
|
|
323
|
+
border: `1px solid ${PALETTE.border}`,
|
|
324
|
+
borderRadius: RADIUS.lg,
|
|
325
|
+
boxShadow: SHADOW.xs,
|
|
326
|
+
overflow: "hidden"
|
|
327
|
+
},
|
|
328
|
+
children: [
|
|
329
|
+
/* @__PURE__ */ jsx(Pagination, {}),
|
|
330
|
+
/* @__PURE__ */ jsxs("div", { style: {
|
|
331
|
+
display: "grid",
|
|
332
|
+
gridTemplateColumns: "minmax(140px, 180px) minmax(120px, 200px) 110px minmax(180px, 1.2fr) minmax(180px, 1.5fr) minmax(180px, 1.5fr) 100px",
|
|
333
|
+
padding: "12px 16px",
|
|
334
|
+
gap: 12,
|
|
335
|
+
background: PALETTE.surfaceMuted,
|
|
336
|
+
fontSize: 11,
|
|
337
|
+
fontWeight: 700,
|
|
338
|
+
letterSpacing: 0.6,
|
|
339
|
+
textTransform: "uppercase",
|
|
340
|
+
color: PALETTE.textMuted,
|
|
341
|
+
borderBottom: `1px solid ${PALETTE.border}`
|
|
342
|
+
}, children: [
|
|
343
|
+
/* @__PURE__ */ jsx("div", { children: "Time" }),
|
|
344
|
+
/* @__PURE__ */ jsx("div", { children: "Actor" }),
|
|
345
|
+
/* @__PURE__ */ jsx("div", { style: { paddingLeft: 22 }, children: "Action" }),
|
|
346
|
+
/* @__PURE__ */ jsx("div", { children: "Path" }),
|
|
347
|
+
/* @__PURE__ */ jsx("div", { children: "Old" }),
|
|
348
|
+
/* @__PURE__ */ jsx("div", { children: "New" }),
|
|
349
|
+
/* @__PURE__ */ jsx("div", { children: "Revert" })
|
|
350
|
+
] }),
|
|
351
|
+
loading && items.length === 0 ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", margin: "size-400", children: /* @__PURE__ */ jsx(ProgressCircle, { "aria-label": "Loading", isIndeterminate: true }) }) : items.length === 0 ? /* @__PURE__ */ jsx(View, { padding: "size-400", children: /* @__PURE__ */ jsx(Text, { UNSAFE_style: { color: PALETTE.textMuted }, children: "No audit entries match these filters." }) }) : items.map((row, i) => {
|
|
352
|
+
const isEncrypted = row.oldValue === SENSITIVE_TOKEN || row.newValue === SENSITIVE_TOKEN;
|
|
353
|
+
const cell = {
|
|
354
|
+
whiteSpace: "pre-wrap",
|
|
355
|
+
overflowWrap: "anywhere",
|
|
356
|
+
wordBreak: "break-word",
|
|
357
|
+
minWidth: 0
|
|
358
|
+
};
|
|
359
|
+
return /* @__PURE__ */ jsxs(
|
|
360
|
+
"div",
|
|
361
|
+
{
|
|
362
|
+
style: {
|
|
363
|
+
display: "grid",
|
|
364
|
+
// Min/max so columns flex but never collapse below readable.
|
|
365
|
+
gridTemplateColumns: "minmax(140px, 180px) minmax(120px, 200px) 110px minmax(180px, 1.2fr) minmax(180px, 1.5fr) minmax(180px, 1.5fr) 100px",
|
|
366
|
+
padding: "12px 16px",
|
|
367
|
+
gap: 12,
|
|
368
|
+
borderBottom: `1px solid ${PALETTE.border}`,
|
|
369
|
+
fontSize: 13,
|
|
370
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
371
|
+
background: i % 2 === 0 ? PALETTE.surface : PALETTE.surfaceSubtle,
|
|
372
|
+
alignItems: "start"
|
|
373
|
+
},
|
|
374
|
+
children: [
|
|
375
|
+
/* @__PURE__ */ jsx("div", { style: { ...cell, color: PALETTE.text }, children: fmtTime(row.changedAt) }),
|
|
376
|
+
/* @__PURE__ */ jsx("div", { style: { ...cell, color: PALETTE.textMuted }, children: row.changedBy || "system" }),
|
|
377
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(StatusLight, { variant: actionTone(row.action), children: row.action || "?" }) }),
|
|
378
|
+
/* @__PURE__ */ jsxs("div", { style: cell, children: [
|
|
379
|
+
/* @__PURE__ */ jsx("div", { children: row.path }),
|
|
380
|
+
/* @__PURE__ */ jsxs("div", { style: { color: PALETTE.textMuted, fontSize: 11 }, children: [
|
|
381
|
+
row.scope,
|
|
382
|
+
":",
|
|
383
|
+
row.scope_id
|
|
384
|
+
] })
|
|
385
|
+
] }),
|
|
386
|
+
/* @__PURE__ */ jsx("div", { style: { ...cell, color: PALETTE.danger }, children: fmtValue(row.oldValue) }),
|
|
387
|
+
/* @__PURE__ */ jsx("div", { style: { ...cell, color: PALETTE.success }, children: fmtValue(row.newValue) }),
|
|
388
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
389
|
+
Button,
|
|
390
|
+
{
|
|
391
|
+
variant: "secondary",
|
|
392
|
+
onPress: () => startRevert(row),
|
|
393
|
+
isDisabled: reverting || isEncrypted,
|
|
394
|
+
UNSAFE_style: { fontFamily: "inherit" },
|
|
395
|
+
children: isEncrypted ? "N/A" : "Revert"
|
|
396
|
+
}
|
|
397
|
+
) })
|
|
398
|
+
]
|
|
399
|
+
},
|
|
400
|
+
row._id || `${row.changedAt}-${row.path}-${i}`
|
|
401
|
+
);
|
|
402
|
+
})
|
|
403
|
+
]
|
|
404
|
+
}
|
|
405
|
+
),
|
|
406
|
+
/* @__PURE__ */ jsxs(DialogTrigger, { isOpen: !!confirmRow, onOpenChange: (o) => {
|
|
407
|
+
if (!o) setConfirmRow(null);
|
|
408
|
+
}, children: [
|
|
409
|
+
/* @__PURE__ */ jsx("div", { style: { display: "none" }, "aria-hidden": "true", children: "trigger" }),
|
|
410
|
+
/* @__PURE__ */ jsxs(Dialog, { children: [
|
|
411
|
+
/* @__PURE__ */ jsx(Heading, { children: "Revert this change?" }),
|
|
412
|
+
/* @__PURE__ */ jsx(Header, { children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
413
|
+
confirmRow == null ? void 0 : confirmRow.path,
|
|
414
|
+
" at ",
|
|
415
|
+
confirmRow == null ? void 0 : confirmRow.scope,
|
|
416
|
+
":",
|
|
417
|
+
confirmRow == null ? void 0 : confirmRow.scope_id
|
|
418
|
+
] }) }),
|
|
419
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
420
|
+
/* @__PURE__ */ jsxs(Content, { children: [
|
|
421
|
+
/* @__PURE__ */ jsx(Text, { children: (confirmRow == null ? void 0 : confirmRow.action) === "create" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
422
|
+
"This will ",
|
|
423
|
+
/* @__PURE__ */ jsx("strong", { children: "delete" }),
|
|
424
|
+
" the scope-level override and fall back to the inherited default."
|
|
425
|
+
] }) : (confirmRow == null ? void 0 : confirmRow.action) === "delete" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
426
|
+
"This will ",
|
|
427
|
+
/* @__PURE__ */ jsx("strong", { children: "re-insert" }),
|
|
428
|
+
" the previous value."
|
|
429
|
+
] }) : /* @__PURE__ */ jsx(Fragment, { children: "This will replace the current value with the previous value." }) }),
|
|
430
|
+
/* @__PURE__ */ jsxs("div", { style: {
|
|
431
|
+
marginTop: 12,
|
|
432
|
+
padding: 12,
|
|
433
|
+
background: PALETTE.surfaceMuted,
|
|
434
|
+
border: `1px solid ${PALETTE.border}`,
|
|
435
|
+
borderRadius: RADIUS.md,
|
|
436
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
437
|
+
fontSize: 12,
|
|
438
|
+
wordBreak: "break-all"
|
|
439
|
+
}, children: [
|
|
440
|
+
/* @__PURE__ */ jsx("div", { style: { color: PALETTE.textMuted, fontSize: 11, fontWeight: 700, letterSpacing: 0.4, textTransform: "uppercase", marginBottom: 4 }, children: "will be set to" }),
|
|
441
|
+
/* @__PURE__ */ jsx("div", { style: { color: PALETTE.success }, children: (confirmRow == null ? void 0 : confirmRow.action) === "create" ? "(inherit from default)" : fmtValue(confirmRow == null ? void 0 : confirmRow.oldValue) })
|
|
442
|
+
] })
|
|
443
|
+
] }),
|
|
444
|
+
/* @__PURE__ */ jsxs(ButtonGroup, { children: [
|
|
445
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onPress: () => setConfirmRow(null), isDisabled: reverting, children: "Cancel" }),
|
|
446
|
+
/* @__PURE__ */ jsx(Button, { variant: "cta", onPress: () => doRevert(confirmRow), isDisabled: reverting, children: reverting ? "Reverting\u2026" : "Revert" })
|
|
447
|
+
] })
|
|
448
|
+
] })
|
|
449
|
+
] })
|
|
450
|
+
] });
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// web/index.js
|
|
454
|
+
function registerAuditLog() {
|
|
455
|
+
configureWeb({
|
|
456
|
+
actionKeys: {
|
|
457
|
+
systemConfigAuditList: "AuditLog/system-config-audit-list"
|
|
458
|
+
},
|
|
459
|
+
extraNav: [{
|
|
460
|
+
id: "audit-log",
|
|
461
|
+
path: "/audit-log",
|
|
462
|
+
label: "Audit Log",
|
|
463
|
+
icon: "Properties"
|
|
464
|
+
}],
|
|
465
|
+
extraPages: { "audit-log": AuditLog }
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
export {
|
|
469
|
+
AuditLog,
|
|
470
|
+
registerAuditLog as default
|
|
471
|
+
};
|