@everscribe/components-react 0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1935 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var componentsCore = require('@everscribe/components-core');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var reactDom = require('react-dom');
7
+
8
+ // src/AuditTrail.tsx
9
+ function useNLP(opts) {
10
+ const [state, setState] = react.useState({ phase: "idle" });
11
+ const ctrlRef = react.useRef(null);
12
+ const submit = react.useCallback(
13
+ async (query) => {
14
+ const trimmed = query.trim();
15
+ if (!trimmed) return;
16
+ if (!opts.token) {
17
+ setState({ phase: "error", query: trimmed, error: "not_configured" });
18
+ return;
19
+ }
20
+ ctrlRef.current?.abort();
21
+ const ctrl = new AbortController();
22
+ ctrlRef.current = ctrl;
23
+ setState({ phase: "loading" });
24
+ try {
25
+ const result = await componentsCore.generateNLPFilters({
26
+ apiBase: opts.apiBase,
27
+ token: opts.token,
28
+ query: trimmed,
29
+ signal: ctrl.signal
30
+ });
31
+ if (ctrl.signal.aborted) return;
32
+ setState({ phase: "ready", query: trimmed, result });
33
+ } catch (err) {
34
+ if (err instanceof DOMException && err.name === "AbortError") return;
35
+ if (err instanceof componentsCore.EmbedError && err.kind === "unauthorized") {
36
+ const refreshed = await componentsCore.fetchTokenViaOpts({
37
+ tokenEndpoint: opts.tokenEndpoint,
38
+ onTokenExpired: opts.onTokenExpired
39
+ });
40
+ if (refreshed && !ctrl.signal.aborted) {
41
+ try {
42
+ const retry = await componentsCore.generateNLPFilters({
43
+ apiBase: opts.apiBase,
44
+ token: refreshed,
45
+ query: trimmed,
46
+ signal: ctrl.signal
47
+ });
48
+ setState({ phase: "ready", query: trimmed, result: retry });
49
+ return;
50
+ } catch (retryErr) {
51
+ setState({
52
+ phase: "error",
53
+ query: trimmed,
54
+ error: classifyNLPError(retryErr)
55
+ });
56
+ return;
57
+ }
58
+ }
59
+ }
60
+ setState({ phase: "error", query: trimmed, error: classifyNLPError(err) });
61
+ }
62
+ },
63
+ [opts.apiBase, opts.token, opts.tokenEndpoint, opts.onTokenExpired]
64
+ );
65
+ const reset = react.useCallback(() => {
66
+ ctrlRef.current?.abort();
67
+ setState({ phase: "idle" });
68
+ }, []);
69
+ return { state, submit, reset };
70
+ }
71
+ function classifyNLPError(err) {
72
+ if (!(err instanceof componentsCore.EmbedError)) return "unknown";
73
+ if (err.status === 503) {
74
+ if (err.message?.includes("busy")) return "provider_busy";
75
+ return "not_configured";
76
+ }
77
+ if (err.status === 429) return "rate_limited";
78
+ if (err.status === 403) return "not_allowed";
79
+ if (err.kind === "bad_request") return "bad_request";
80
+ return "unknown";
81
+ }
82
+ var TIME_PRESETS = [
83
+ { key: "24h", label: "24h" },
84
+ { key: "7d", label: "7d" },
85
+ { key: "30d", label: "30d" },
86
+ { key: "custom", label: "Custom" },
87
+ { key: "all", label: "All" }
88
+ ];
89
+ function pickInitialTab(v, claims) {
90
+ if (v.nlpQ && claims?.allow_nlp) return "ai";
91
+ if (v.q && claims?.allow_dsl_input) return "query";
92
+ if (hasAnyColumnFilter(v)) return "filters";
93
+ if (claims?.allow_nlp) return "ai";
94
+ return "filters";
95
+ }
96
+ function FiltersPanel({
97
+ value,
98
+ onChange,
99
+ distinct,
100
+ claims,
101
+ apiBase,
102
+ token,
103
+ tokenEndpoint,
104
+ onTokenExpired,
105
+ onApplied
106
+ }) {
107
+ const allowNLP = !!claims?.allow_nlp;
108
+ const allowDSL = !!claims?.allow_dsl_input;
109
+ const [activeTab, setActiveTab] = react.useState(
110
+ () => pickInitialTab(value, claims)
111
+ );
112
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-filter-panel", children: [
113
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-filter-modes", role: "tablist", "aria-label": "Filter mode", children: [
114
+ allowNLP && /* @__PURE__ */ jsxRuntime.jsx(
115
+ "button",
116
+ {
117
+ type: "button",
118
+ role: "tab",
119
+ "aria-selected": activeTab === "ai",
120
+ className: tabClass(activeTab === "ai"),
121
+ onClick: () => setActiveTab("ai"),
122
+ children: "Prompt"
123
+ }
124
+ ),
125
+ /* @__PURE__ */ jsxRuntime.jsx(
126
+ "button",
127
+ {
128
+ type: "button",
129
+ role: "tab",
130
+ "aria-selected": activeTab === "filters",
131
+ className: tabClass(activeTab === "filters"),
132
+ onClick: () => setActiveTab("filters"),
133
+ children: "Filters"
134
+ }
135
+ ),
136
+ allowDSL && /* @__PURE__ */ jsxRuntime.jsx(
137
+ "button",
138
+ {
139
+ type: "button",
140
+ role: "tab",
141
+ "aria-selected": activeTab === "query",
142
+ className: tabClass(activeTab === "query"),
143
+ onClick: () => setActiveTab("query"),
144
+ children: "Query"
145
+ }
146
+ )
147
+ ] }),
148
+ activeTab === "ai" && allowNLP && /* @__PURE__ */ jsxRuntime.jsx(
149
+ AITabPanel,
150
+ {
151
+ value,
152
+ onChange,
153
+ apiBase,
154
+ token,
155
+ tokenEndpoint,
156
+ onTokenExpired
157
+ }
158
+ ),
159
+ activeTab === "filters" && /* @__PURE__ */ jsxRuntime.jsx(
160
+ FiltersTabPanel,
161
+ {
162
+ value,
163
+ onChange,
164
+ distinct,
165
+ onApplied
166
+ }
167
+ ),
168
+ activeTab === "query" && allowDSL && /* @__PURE__ */ jsxRuntime.jsx(QueryTabPanel, { value, onChange })
169
+ ] });
170
+ }
171
+ function tabClass(active) {
172
+ return active ? "audit-trail-filter-mode audit-trail-filter-mode-selected" : "audit-trail-filter-mode";
173
+ }
174
+ function AITabPanel({
175
+ value,
176
+ onChange,
177
+ apiBase,
178
+ token,
179
+ tokenEndpoint,
180
+ onTokenExpired
181
+ }) {
182
+ const [draft, setDraft] = react.useState(value.nlpQ ?? "");
183
+ const nlp = useNLP({ apiBase, token, tokenEndpoint, onTokenExpired });
184
+ const lastAppliedRef = react.useRef(null);
185
+ react.useEffect(() => {
186
+ if (nlp.state.phase !== "ready") return;
187
+ const sig = nlp.state.query + "|" + (nlp.state.result.dsl ?? "");
188
+ if (lastAppliedRef.current === sig) return;
189
+ lastAppliedRef.current = sig;
190
+ onChange({
191
+ ...value,
192
+ q: nlp.state.result.dsl || void 0,
193
+ nlpQ: nlp.state.query,
194
+ nlpExplanation: nlp.state.result.explanation,
195
+ nlpUnsupported: nlp.state.result.unsupported
196
+ });
197
+ }, [nlp.state, onChange, value]);
198
+ const isBusy = nlp.state.phase === "loading";
199
+ const handleSubmit = react.useCallback(
200
+ (e) => {
201
+ e.preventDefault();
202
+ void nlp.submit(draft);
203
+ },
204
+ [draft, nlp]
205
+ );
206
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "audit-trail-nlp-form", onSubmit: handleSubmit, children: [
207
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-nlp-label", htmlFor: "audit-trail-nlp-input", children: [
208
+ "Ask in plain English",
209
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-badge audit-trail-badge-info", title: "Beta", children: "Beta" })
210
+ ] }),
211
+ /* @__PURE__ */ jsxRuntime.jsx(
212
+ "input",
213
+ {
214
+ id: "audit-trail-nlp-input",
215
+ type: "text",
216
+ className: "audit-trail-nlp-input",
217
+ placeholder: "e.g. failed logins last 24 hours",
218
+ value: draft,
219
+ onChange: (e) => setDraft(e.target.value),
220
+ maxLength: 500,
221
+ "aria-label": "Natural-language filter query",
222
+ disabled: isBusy
223
+ }
224
+ ),
225
+ value.q && value.nlpQ && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-nlp-banner", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
226
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Translated to:" }),
227
+ " ",
228
+ /* @__PURE__ */ jsxRuntime.jsx("code", { className: "audit-trail-nlp-translated", children: value.q })
229
+ ] }) }),
230
+ nlp.state.phase === "error" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-error", children: nlpErrorMessage(nlp.state.error) }),
231
+ value.nlpUnsupported && value.nlpUnsupported.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-error audit-trail-nlp-unsupported", children: /* @__PURE__ */ jsxRuntime.jsx("ul", { children: value.nlpUnsupported.map((u, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
232
+ "Couldn't apply: ",
233
+ u
234
+ ] }, i)) }) }),
235
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-filter-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
236
+ "button",
237
+ {
238
+ type: "submit",
239
+ className: "audit-trail-button audit-trail-button-secondary",
240
+ disabled: !draft.trim() || isBusy,
241
+ children: isBusy ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
242
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-button-spinner" }),
243
+ " Translating\u2026"
244
+ ] }) : "Search"
245
+ }
246
+ ) })
247
+ ] });
248
+ }
249
+ function nlpErrorMessage(reason) {
250
+ switch (reason) {
251
+ case "not_configured":
252
+ return "Natural-language filtering isn't configured on this server.";
253
+ case "not_allowed":
254
+ return "This embed token does not permit natural-language queries.";
255
+ case "rate_limited":
256
+ return "You've hit the per-hour limit. Try again in a few minutes.";
257
+ case "provider_busy":
258
+ return "The natural-language service is busy right now. Try again in a moment.";
259
+ case "bad_request":
260
+ return "That query couldn't be processed. Try rephrasing.";
261
+ default:
262
+ return "Couldn't process that query. Try simpler terms.";
263
+ }
264
+ }
265
+ function FiltersTabPanel({ value, onChange, distinct, onApplied }) {
266
+ const [draftSince, setDraftSince] = react.useState(value.since ?? "");
267
+ const [draftBefore, setDraftBefore] = react.useState(value.before ?? "");
268
+ const [draftAction, setDraftAction] = react.useState(value.action ?? "");
269
+ const [draftActorType, setDraftActorType] = react.useState(value.actorType ?? "");
270
+ const [draftTenantID, setDraftTenantID] = react.useState(value.tenantId ?? "");
271
+ const [draftTargetType, setDraftTargetType] = react.useState(value.targetType ?? "");
272
+ const [draftResultStatus, setDraftResultStatus] = react.useState(value.resultStatus ?? "");
273
+ const [draftActor, setDraftActor] = react.useState(value.actor ?? "");
274
+ const [draftTargetID, setDraftTargetID] = react.useState(value.targetId ?? "");
275
+ const [draftOriginIP, setDraftOriginIP] = react.useState(value.originIP ?? "");
276
+ react.useEffect(() => {
277
+ setDraftSince(value.since ?? "");
278
+ setDraftBefore(value.before ?? "");
279
+ setDraftAction(value.action ?? "");
280
+ setDraftActorType(value.actorType ?? "");
281
+ setDraftTenantID(value.tenantId ?? "");
282
+ setDraftTargetType(value.targetType ?? "");
283
+ setDraftResultStatus(value.resultStatus ?? "");
284
+ setDraftActor(value.actor ?? "");
285
+ setDraftTargetID(value.targetId ?? "");
286
+ setDraftOriginIP(value.originIP ?? "");
287
+ }, [
288
+ value.since,
289
+ value.before,
290
+ value.action,
291
+ value.actorType,
292
+ value.tenantId,
293
+ value.targetType,
294
+ value.resultStatus,
295
+ value.actor,
296
+ value.targetId,
297
+ value.originIP
298
+ ]);
299
+ const norm = (s) => s === "" ? void 0 : s;
300
+ const dirty = norm(draftAction) !== value.action || norm(draftActorType) !== value.actorType || norm(draftTenantID) !== value.tenantId || norm(draftTargetType) !== value.targetType || norm(draftResultStatus) !== value.resultStatus || norm(draftActor) !== value.actor || norm(draftTargetID) !== value.targetId || norm(draftOriginIP) !== value.originIP || value.range === "custom" && (norm(draftSince) !== value.since || norm(draftBefore) !== value.before);
301
+ const handleApply = () => {
302
+ onChange({
303
+ ...value,
304
+ action: norm(draftAction),
305
+ actorType: norm(draftActorType),
306
+ tenantId: norm(draftTenantID),
307
+ targetType: norm(draftTargetType),
308
+ resultStatus: norm(draftResultStatus),
309
+ actor: norm(draftActor),
310
+ targetId: norm(draftTargetID),
311
+ originIP: norm(draftOriginIP),
312
+ since: value.range === "custom" ? norm(draftSince) : value.since,
313
+ before: value.range === "custom" ? norm(draftBefore) : value.before,
314
+ q: void 0,
315
+ nlpQ: void 0,
316
+ nlpExplanation: void 0,
317
+ nlpUnsupported: void 0
318
+ });
319
+ onApplied?.();
320
+ };
321
+ const handlePresetClick = (range) => {
322
+ if (range === "custom") {
323
+ onChange({ ...value, range });
324
+ return;
325
+ }
326
+ onChange({ ...value, range, since: void 0, before: void 0 });
327
+ };
328
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-filter-tab-panel", children: [
329
+ /* @__PURE__ */ jsxRuntime.jsx(
330
+ "div",
331
+ {
332
+ className: "audit-trail-filter-tabs",
333
+ role: "tablist",
334
+ "aria-label": "Time range",
335
+ children: TIME_PRESETS.map((p) => /* @__PURE__ */ jsxRuntime.jsx(
336
+ "button",
337
+ {
338
+ type: "button",
339
+ role: "tab",
340
+ "aria-selected": value.range === p.key,
341
+ className: value.range === p.key ? "audit-trail-filter-tab audit-trail-filter-tab-selected" : "audit-trail-filter-tab",
342
+ onClick: () => handlePresetClick(p.key),
343
+ children: p.label
344
+ },
345
+ p.key
346
+ ))
347
+ }
348
+ ),
349
+ value.range === "custom" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-filter-row", children: [
350
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-filter-field", children: [
351
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-filter-field-label", children: "From" }),
352
+ /* @__PURE__ */ jsxRuntime.jsx(
353
+ "input",
354
+ {
355
+ type: "datetime-local",
356
+ className: "audit-trail-filter-input",
357
+ value: draftSince,
358
+ onChange: (e) => setDraftSince(e.target.value)
359
+ }
360
+ )
361
+ ] }),
362
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-filter-field", children: [
363
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-filter-field-label", children: "To" }),
364
+ /* @__PURE__ */ jsxRuntime.jsx(
365
+ "input",
366
+ {
367
+ type: "datetime-local",
368
+ className: "audit-trail-filter-input",
369
+ value: draftBefore,
370
+ onChange: (e) => setDraftBefore(e.target.value)
371
+ }
372
+ )
373
+ ] })
374
+ ] }),
375
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-filter-row", children: [
376
+ /* @__PURE__ */ jsxRuntime.jsxs(
377
+ "select",
378
+ {
379
+ className: "audit-trail-filter-select",
380
+ "aria-label": "Action",
381
+ value: draftAction,
382
+ onChange: (e) => setDraftAction(e.target.value),
383
+ children: [
384
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All actions" }),
385
+ distinct.actions.map((a) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: a, children: a }, a))
386
+ ]
387
+ }
388
+ ),
389
+ /* @__PURE__ */ jsxRuntime.jsxs(
390
+ "select",
391
+ {
392
+ className: "audit-trail-filter-select",
393
+ "aria-label": "Actor type",
394
+ value: draftActorType,
395
+ onChange: (e) => setDraftActorType(e.target.value),
396
+ children: [
397
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All actor types" }),
398
+ distinct.actorTypes.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: t, children: t }, t))
399
+ ]
400
+ }
401
+ ),
402
+ /* @__PURE__ */ jsxRuntime.jsxs(
403
+ "select",
404
+ {
405
+ className: "audit-trail-filter-select",
406
+ "aria-label": "Target type",
407
+ value: draftTargetType,
408
+ onChange: (e) => setDraftTargetType(e.target.value),
409
+ children: [
410
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All target types" }),
411
+ distinct.targetTypes.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: t, children: t }, t))
412
+ ]
413
+ }
414
+ ),
415
+ distinct.tenants.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
416
+ "select",
417
+ {
418
+ className: "audit-trail-filter-select",
419
+ "aria-label": "Tenant",
420
+ value: draftTenantID,
421
+ onChange: (e) => setDraftTenantID(e.target.value),
422
+ children: [
423
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All tenants" }),
424
+ distinct.tenants.map((t) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: t, children: t }, t))
425
+ ]
426
+ }
427
+ ),
428
+ distinct.resultStatuses.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
429
+ "select",
430
+ {
431
+ className: "audit-trail-filter-select",
432
+ "aria-label": "Result",
433
+ value: draftResultStatus,
434
+ onChange: (e) => setDraftResultStatus(e.target.value),
435
+ children: [
436
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "All results" }),
437
+ distinct.resultStatuses.map((s) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: s, children: s }, s))
438
+ ]
439
+ }
440
+ )
441
+ ] }),
442
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-filter-row", children: /* @__PURE__ */ jsxRuntime.jsx(
443
+ "input",
444
+ {
445
+ type: "text",
446
+ className: "audit-trail-filter-input audit-trail-filter-actor-input",
447
+ placeholder: "Actor (id, name, email)",
448
+ value: draftActor,
449
+ onChange: (e) => setDraftActor(e.target.value)
450
+ }
451
+ ) }),
452
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-filter-row", children: /* @__PURE__ */ jsxRuntime.jsx(
453
+ "input",
454
+ {
455
+ type: "text",
456
+ className: "audit-trail-filter-input audit-trail-filter-wide-input",
457
+ placeholder: "Target ID",
458
+ value: draftTargetID,
459
+ onChange: (e) => setDraftTargetID(e.target.value)
460
+ }
461
+ ) }),
462
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-filter-row", children: /* @__PURE__ */ jsxRuntime.jsx(
463
+ "input",
464
+ {
465
+ type: "text",
466
+ className: "audit-trail-filter-input audit-trail-filter-wide-input",
467
+ placeholder: "Origin IP",
468
+ value: draftOriginIP,
469
+ onChange: (e) => setDraftOriginIP(e.target.value)
470
+ }
471
+ ) }),
472
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-filter-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
473
+ "button",
474
+ {
475
+ type: "button",
476
+ className: "audit-trail-button audit-trail-button-secondary",
477
+ disabled: !dirty,
478
+ onClick: handleApply,
479
+ children: "Add filters"
480
+ }
481
+ ) }),
482
+ /* @__PURE__ */ jsxRuntime.jsx(
483
+ MetadataFilterSection,
484
+ {
485
+ value,
486
+ onChange,
487
+ distinct,
488
+ onApplied
489
+ }
490
+ )
491
+ ] });
492
+ }
493
+ function MetadataFilterSection({
494
+ value,
495
+ onChange,
496
+ distinct,
497
+ onApplied
498
+ }) {
499
+ const [variant, setVariant] = react.useState("metadata");
500
+ const [mdKey, setMdKey] = react.useState("");
501
+ const [mdOp, setMdOp] = react.useState("eq");
502
+ const [mdValue, setMdValue] = react.useState("");
503
+ const [mdValue2, setMdValue2] = react.useState("");
504
+ const [cfField, setCfField] = react.useState("");
505
+ const [cbField, setCbField] = react.useState("");
506
+ const [cbValue, setCbValue] = react.useState("");
507
+ const [caField, setCaField] = react.useState("");
508
+ const [caValue, setCaValue] = react.useState("");
509
+ const compiled = compileMetaClause({
510
+ variant,
511
+ mdKey,
512
+ mdOp,
513
+ mdValue,
514
+ mdValue2,
515
+ cfField,
516
+ cbField,
517
+ cbValue,
518
+ caField,
519
+ caValue
520
+ });
521
+ const submittable = compiled.dsl !== null;
522
+ const showError = !submittable && compiled.error && /Both bounds/.test(compiled.error);
523
+ const handleAdd = () => {
524
+ if (!compiled.dsl) return;
525
+ onChange({
526
+ ...value,
527
+ q: compiled.dsl,
528
+ nlpQ: void 0,
529
+ nlpExplanation: void 0,
530
+ nlpUnsupported: void 0
531
+ });
532
+ onApplied?.();
533
+ };
534
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-add-filter-inline", children: [
535
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "audit-trail-add-filter-heading", children: "Metadata and Changed Fields" }),
536
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
537
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Filter type" }),
538
+ /* @__PURE__ */ jsxRuntime.jsxs(
539
+ "select",
540
+ {
541
+ value: variant,
542
+ onChange: (e) => setVariant(e.target.value),
543
+ children: [
544
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "metadata", children: "Metadata key/value" }),
545
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "change-field", children: "Change: a field changed" }),
546
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "change-before", children: "Change: previous value (before)" }),
547
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "change-after", children: "Change: new value (after)" })
548
+ ]
549
+ }
550
+ )
551
+ ] }),
552
+ variant === "metadata" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
553
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
554
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Key" }),
555
+ /* @__PURE__ */ jsxRuntime.jsx(
556
+ "input",
557
+ {
558
+ type: "text",
559
+ list: "audit-trail-mdk-key-list",
560
+ placeholder: "e.g. environment",
561
+ value: mdKey,
562
+ onChange: (e) => setMdKey(e.target.value)
563
+ }
564
+ ),
565
+ /* @__PURE__ */ jsxRuntime.jsx("datalist", { id: "audit-trail-mdk-key-list", children: distinct.metadataKeys.map((k) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: k.key, children: [
566
+ k.observed_type,
567
+ " \xB7 ",
568
+ k.event_count
569
+ ] }, k.key)) })
570
+ ] }),
571
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
572
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Operator" }),
573
+ /* @__PURE__ */ jsxRuntime.jsxs(
574
+ "select",
575
+ {
576
+ value: mdOp,
577
+ onChange: (e) => setMdOp(e.target.value),
578
+ children: [
579
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "eq", children: "equals" }),
580
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "neq", children: "not equal" }),
581
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "contains", children: "contains" }),
582
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "gt", children: "greater than" }),
583
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "gte", children: "greater or equal" }),
584
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "lt", children: "less than" }),
585
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "lte", children: "less or equal" }),
586
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "between", children: "between" })
587
+ ]
588
+ }
589
+ )
590
+ ] }),
591
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
592
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Value" }),
593
+ /* @__PURE__ */ jsxRuntime.jsx(
594
+ "input",
595
+ {
596
+ type: "text",
597
+ placeholder: "e.g. prod",
598
+ value: mdValue,
599
+ onChange: (e) => setMdValue(e.target.value)
600
+ }
601
+ )
602
+ ] }),
603
+ mdOp === "between" && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
604
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Upper bound" }),
605
+ /* @__PURE__ */ jsxRuntime.jsx(
606
+ "input",
607
+ {
608
+ type: "text",
609
+ placeholder: "e.g. 1000",
610
+ value: mdValue2,
611
+ onChange: (e) => setMdValue2(e.target.value)
612
+ }
613
+ )
614
+ ] })
615
+ ] }),
616
+ variant === "change-field" && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
617
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Field" }),
618
+ /* @__PURE__ */ jsxRuntime.jsx(
619
+ "input",
620
+ {
621
+ type: "text",
622
+ list: "audit-trail-cf-field-list",
623
+ placeholder: "e.g. email",
624
+ value: cfField,
625
+ onChange: (e) => setCfField(e.target.value)
626
+ }
627
+ ),
628
+ /* @__PURE__ */ jsxRuntime.jsx("datalist", { id: "audit-trail-cf-field-list", children: distinct.changeFields.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.field, children: [
629
+ c.event_count,
630
+ " events"
631
+ ] }, c.field)) })
632
+ ] }),
633
+ variant === "change-before" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
634
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
635
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Field" }),
636
+ /* @__PURE__ */ jsxRuntime.jsx(
637
+ "input",
638
+ {
639
+ type: "text",
640
+ list: "audit-trail-cf-field-list",
641
+ placeholder: "e.g. role",
642
+ value: cbField,
643
+ onChange: (e) => setCbField(e.target.value)
644
+ }
645
+ )
646
+ ] }),
647
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
648
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Previous value" }),
649
+ /* @__PURE__ */ jsxRuntime.jsx(
650
+ "input",
651
+ {
652
+ type: "text",
653
+ placeholder: "e.g. user",
654
+ value: cbValue,
655
+ onChange: (e) => setCbValue(e.target.value)
656
+ }
657
+ )
658
+ ] })
659
+ ] }),
660
+ variant === "change-after" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
661
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
662
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "Field" }),
663
+ /* @__PURE__ */ jsxRuntime.jsx(
664
+ "input",
665
+ {
666
+ type: "text",
667
+ list: "audit-trail-cf-field-list",
668
+ placeholder: "e.g. role",
669
+ value: caField,
670
+ onChange: (e) => setCaField(e.target.value)
671
+ }
672
+ )
673
+ ] }),
674
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-add-filter-row", children: [
675
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-add-filter-label", children: "New value" }),
676
+ /* @__PURE__ */ jsxRuntime.jsx(
677
+ "input",
678
+ {
679
+ type: "text",
680
+ placeholder: "e.g. admin",
681
+ value: caValue,
682
+ onChange: (e) => setCaValue(e.target.value)
683
+ }
684
+ )
685
+ ] })
686
+ ] }),
687
+ showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "audit-trail-add-filter-error", children: compiled.error }),
688
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-filter-actions audit-trail-add-filter-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
689
+ "button",
690
+ {
691
+ type: "button",
692
+ className: "audit-trail-button audit-trail-button-secondary",
693
+ disabled: !submittable,
694
+ onClick: handleAdd,
695
+ children: "Add filter"
696
+ }
697
+ ) })
698
+ ] });
699
+ }
700
+ function compileMetaClause(i) {
701
+ switch (i.variant) {
702
+ case "metadata": {
703
+ const key = i.mdKey.trim();
704
+ if (!key) return { dsl: null, error: "Key is required" };
705
+ const path = "metadata." + quoteKeyIfNeeded(key);
706
+ const v1 = i.mdValue;
707
+ const v2 = i.mdValue2;
708
+ switch (i.mdOp) {
709
+ case "eq":
710
+ return { dsl: `${path}:${quoteIfNeeded(v1)}` };
711
+ case "neq":
712
+ return { dsl: `${path}:!${quoteIfNeeded(v1)}` };
713
+ case "contains":
714
+ return { dsl: `${path}:~${quoteIfNeeded(v1)}` };
715
+ case "gt":
716
+ return { dsl: `${path}:>${quoteIfNeeded(v1)}` };
717
+ case "gte":
718
+ return { dsl: `${path}:>=${quoteIfNeeded(v1)}` };
719
+ case "lt":
720
+ return { dsl: `${path}:<${quoteIfNeeded(v1)}` };
721
+ case "lte":
722
+ return { dsl: `${path}:<=${quoteIfNeeded(v1)}` };
723
+ case "between":
724
+ if (v1 === "" || v2 === "") {
725
+ return { dsl: null, error: "Both bounds required for between" };
726
+ }
727
+ return {
728
+ dsl: `${path}:[${quoteIfNeeded(v1)} TO ${quoteIfNeeded(v2)}]`
729
+ };
730
+ }
731
+ return { dsl: null, error: "Unknown operator" };
732
+ }
733
+ case "change-field": {
734
+ const f = i.cfField.trim();
735
+ if (!f) return { dsl: null, error: "Field is required" };
736
+ return { dsl: `change.field:${quoteIfNeeded(f)}` };
737
+ }
738
+ case "change-before": {
739
+ const f = i.cbField.trim();
740
+ if (!f) return { dsl: null, error: "Field is required" };
741
+ return {
742
+ dsl: `change.${quoteKeyIfNeeded(f)}.before:${quoteIfNeeded(i.cbValue)}`
743
+ };
744
+ }
745
+ case "change-after": {
746
+ const f = i.caField.trim();
747
+ if (!f) return { dsl: null, error: "Field is required" };
748
+ return {
749
+ dsl: `change.${quoteKeyIfNeeded(f)}.after:${quoteIfNeeded(i.caValue)}`
750
+ };
751
+ }
752
+ }
753
+ }
754
+ function quoteIfNeeded(raw) {
755
+ if (raw === "") return '""';
756
+ if (/[\s"\(\)\[\]]/.test(raw)) {
757
+ return '"' + raw.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
758
+ }
759
+ return raw;
760
+ }
761
+ function quoteKeyIfNeeded(raw) {
762
+ if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(raw)) return raw;
763
+ return '"' + raw + '"';
764
+ }
765
+ function QueryTabPanel({ value, onChange }) {
766
+ const [draft, setDraft] = react.useState(value.q ?? "");
767
+ react.useEffect(() => {
768
+ setDraft(value.q ?? "");
769
+ }, [value.q]);
770
+ const handleSubmit = (e) => {
771
+ e.preventDefault();
772
+ const next = draft.trim() || void 0;
773
+ if (next === value.q) return;
774
+ onChange({
775
+ ...value,
776
+ q: next,
777
+ nlpQ: void 0,
778
+ nlpExplanation: void 0,
779
+ nlpUnsupported: void 0
780
+ });
781
+ };
782
+ return /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "audit-trail-query-form", onSubmit: handleSubmit, children: [
783
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-query-label", htmlFor: "audit-trail-query-input", children: [
784
+ "Query",
785
+ " ",
786
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: "https://everscribe.io/docs", target: "_blank", rel: "noopener noreferrer", children: "(see docs)" })
787
+ ] }),
788
+ /* @__PURE__ */ jsxRuntime.jsx(
789
+ "input",
790
+ {
791
+ id: "audit-trail-query-input",
792
+ type: "text",
793
+ className: "audit-trail-query-input",
794
+ placeholder: "e.g. action:user.login AND result.status:!ok",
795
+ value: draft,
796
+ onChange: (e) => setDraft(e.target.value),
797
+ "aria-label": "Advanced query (DSL)",
798
+ spellCheck: false
799
+ }
800
+ ),
801
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-filter-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
802
+ "button",
803
+ {
804
+ type: "submit",
805
+ className: "audit-trail-button audit-trail-button-secondary",
806
+ disabled: (draft.trim() || "") === (value.q ?? ""),
807
+ children: "Search"
808
+ }
809
+ ) })
810
+ ] });
811
+ }
812
+ function hasAnyColumnFilter(v) {
813
+ return !!(v.action || v.actor || v.actorType || v.tenantId || v.targetType || v.targetId || v.resultStatus || v.originIP);
814
+ }
815
+ function countActiveColumnFilters(v) {
816
+ let n = 0;
817
+ if (v.action) n++;
818
+ if (v.actor) n++;
819
+ if (v.actorType) n++;
820
+ if (v.tenantId) n++;
821
+ if (v.targetType) n++;
822
+ if (v.targetId) n++;
823
+ if (v.resultStatus) n++;
824
+ if (v.originIP) n++;
825
+ return n;
826
+ }
827
+ function parseQClauses(q) {
828
+ const trimmed = q.trim();
829
+ if (!trimmed) return [];
830
+ if (/[()]/.test(trimmed)) return [{ label: trimmed, raw: trimmed }];
831
+ return trimmed.split(/\s+AND\s+/i).map((s) => s.trim()).filter((s) => s.length > 0).map((s) => ({ label: s, raw: s }));
832
+ }
833
+ function removeClauseFromQ(q, raw) {
834
+ const remaining = parseQClauses(q).filter((c) => c.raw !== raw);
835
+ if (remaining.length === 0) return void 0;
836
+ return remaining.map((c) => c.raw).join(" AND ");
837
+ }
838
+ function ActiveFilterChips({ value, onChange }) {
839
+ const chips = [];
840
+ const colChip = (field, label, raw) => {
841
+ if (!raw) return;
842
+ chips.push({
843
+ label: `${label}: ${raw}`,
844
+ onRemove: () => onChange({ ...value, [field]: void 0 })
845
+ });
846
+ };
847
+ colChip("action", "Action", value.action);
848
+ colChip("actor", "Actor", value.actor);
849
+ colChip("actorType", "Actor type", value.actorType);
850
+ colChip("tenantId", "Tenant", value.tenantId);
851
+ colChip("targetType", "Target type", value.targetType);
852
+ colChip("targetId", "Target ID", value.targetId);
853
+ colChip("resultStatus", "Result", value.resultStatus);
854
+ colChip("originIP", "Origin IP", value.originIP);
855
+ if (value.q) {
856
+ for (const clause of parseQClauses(value.q)) {
857
+ chips.push({
858
+ label: clause.label,
859
+ onRemove: () => {
860
+ const nextQ = removeClauseFromQ(value.q, clause.raw);
861
+ onChange({
862
+ ...value,
863
+ q: nextQ,
864
+ // If this was the last DSL clause and the q came from an
865
+ // NLP translation, clear the echo fields too - there's
866
+ // nothing left to attribute to "you asked".
867
+ nlpQ: nextQ ? value.nlpQ : void 0,
868
+ nlpExplanation: nextQ ? value.nlpExplanation : void 0,
869
+ nlpUnsupported: nextQ ? value.nlpUnsupported : void 0
870
+ });
871
+ }
872
+ });
873
+ }
874
+ }
875
+ if (chips.length === 0) return null;
876
+ const clearAll = () => onChange({
877
+ range: value.range,
878
+ since: value.since,
879
+ before: value.before
880
+ });
881
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-filter-chips-row", children: [
882
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-filter-chips", children: chips.map((c, i) => /* @__PURE__ */ jsxRuntime.jsxs(
883
+ "button",
884
+ {
885
+ type: "button",
886
+ className: "audit-trail-filter-chip",
887
+ onClick: c.onRemove,
888
+ title: "Remove this filter",
889
+ "aria-label": `Remove filter: ${c.label}`,
890
+ children: [
891
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: c.label }),
892
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "audit-trail-filter-chip-x", children: "\xD7" })
893
+ ]
894
+ },
895
+ i
896
+ )) }),
897
+ /* @__PURE__ */ jsxRuntime.jsx(
898
+ "button",
899
+ {
900
+ type: "button",
901
+ className: "audit-trail-button audit-trail-button-text",
902
+ onClick: clearAll,
903
+ children: "Clear all"
904
+ }
905
+ )
906
+ ] });
907
+ }
908
+ function ColumnPicker({ available, visible, onToggle }) {
909
+ const visibleCount = available.reduce((n, c) => visible.has(c) ? n + 1 : n, 0);
910
+ return /* @__PURE__ */ jsxRuntime.jsxs("details", { className: "audit-trail-picker", children: [
911
+ /* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "audit-trail-picker-summary", children: [
912
+ "Columns (",
913
+ visibleCount,
914
+ "/",
915
+ available.length,
916
+ ")"
917
+ ] }),
918
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-picker-content", children: available.map((col) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "audit-trail-picker-item", children: [
919
+ /* @__PURE__ */ jsxRuntime.jsx(
920
+ "input",
921
+ {
922
+ type: "checkbox",
923
+ checked: visible.has(col),
924
+ onChange: () => onToggle(col)
925
+ }
926
+ ),
927
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: componentsCore.COLUMN_LABELS[col] ?? col })
928
+ ] }, col)) })
929
+ ] });
930
+ }
931
+ function EventDetail({ event, onClose, theme = "light" }) {
932
+ const [mounted, setMounted] = react.useState(false);
933
+ const [tab, setTab] = react.useState("raw");
934
+ const [copied, setCopied] = react.useState(false);
935
+ const closeBtnRef = react.useRef(null);
936
+ const showDiff = componentsCore.hasParseableDiff(event.change);
937
+ const diff = react.useMemo(
938
+ () => showDiff ? componentsCore.renderDiff(event.change) : { lines: [] },
939
+ [event.change, showDiff]
940
+ );
941
+ const rawJson = react.useMemo(() => JSON.stringify(event, null, 2), [event]);
942
+ const highlightedJson = react.useMemo(() => highlightJSON(rawJson), [rawJson]);
943
+ const metadataRows = react.useMemo(() => buildMetadataRows(event.metadata), [event.metadata]);
944
+ const showMetadata = metadataRows.length > 0;
945
+ react.useEffect(() => setMounted(true), []);
946
+ react.useEffect(() => {
947
+ if (!mounted) return;
948
+ closeBtnRef.current?.focus();
949
+ const onKey = (e) => {
950
+ if (e.key === "Escape") onClose();
951
+ };
952
+ document.addEventListener("keydown", onKey);
953
+ return () => document.removeEventListener("keydown", onKey);
954
+ }, [mounted, onClose]);
955
+ const handleCopy = async () => {
956
+ try {
957
+ await navigator.clipboard.writeText(rawJson);
958
+ setCopied(true);
959
+ setTimeout(() => setCopied(false), 1500);
960
+ } catch {
961
+ }
962
+ };
963
+ if (!mounted) return null;
964
+ return reactDom.createPortal(
965
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `audit-trail-portal audit-trail-theme-${theme}`, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-inspect-backdrop", onMouseDown: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs(
966
+ "div",
967
+ {
968
+ className: "audit-trail-inspect-modal",
969
+ role: "dialog",
970
+ "aria-modal": "true",
971
+ "aria-labelledby": "audit-trail-inspect-title",
972
+ onMouseDown: (e) => e.stopPropagation(),
973
+ children: [
974
+ /* @__PURE__ */ jsxRuntime.jsx(
975
+ "button",
976
+ {
977
+ type: "button",
978
+ ref: closeBtnRef,
979
+ className: "audit-trail-inspect-close",
980
+ "aria-label": "Close",
981
+ onClick: onClose,
982
+ children: "\xD7"
983
+ }
984
+ ),
985
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "audit-trail-inspect-title", className: "audit-trail-inspect-title", children: "Inspect Event" }),
986
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "audit-trail-inspect-subtitle", children: [
987
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-inspect-subtitle-line", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: event.action || "-" }) }),
988
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-inspect-subtitle-line", children: formatHeaderTimestamp(event.occurred_at) }),
989
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-inspect-subtitle-line", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: event.id }) })
990
+ ] }),
991
+ (showDiff || showMetadata) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-inspect-tabs", role: "tablist", "aria-label": "View", children: [
992
+ /* @__PURE__ */ jsxRuntime.jsx(
993
+ "button",
994
+ {
995
+ type: "button",
996
+ role: "tab",
997
+ "aria-selected": tab === "raw",
998
+ className: inspectTabClass(tab === "raw"),
999
+ onClick: () => setTab("raw"),
1000
+ children: "Raw"
1001
+ }
1002
+ ),
1003
+ showDiff && /* @__PURE__ */ jsxRuntime.jsx(
1004
+ "button",
1005
+ {
1006
+ type: "button",
1007
+ role: "tab",
1008
+ "aria-selected": tab === "diff",
1009
+ className: inspectTabClass(tab === "diff"),
1010
+ onClick: () => setTab("diff"),
1011
+ children: "Diff"
1012
+ }
1013
+ ),
1014
+ showMetadata && /* @__PURE__ */ jsxRuntime.jsx(
1015
+ "button",
1016
+ {
1017
+ type: "button",
1018
+ role: "tab",
1019
+ "aria-selected": tab === "metadata",
1020
+ className: inspectTabClass(tab === "metadata"),
1021
+ onClick: () => setTab("metadata"),
1022
+ children: "Metadata"
1023
+ }
1024
+ )
1025
+ ] }),
1026
+ tab === "raw" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-inspect-panel", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-code-block-wrap", children: [
1027
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "audit-trail-code-block", children: /* @__PURE__ */ jsxRuntime.jsx("code", { dangerouslySetInnerHTML: { __html: highlightedJson } }) }),
1028
+ /* @__PURE__ */ jsxRuntime.jsx(
1029
+ "button",
1030
+ {
1031
+ type: "button",
1032
+ className: "audit-trail-copy-button",
1033
+ onClick: handleCopy,
1034
+ "aria-label": copied ? "Copied" : "Copy to clipboard",
1035
+ title: copied ? "Copied" : "Copy to clipboard",
1036
+ children: copied ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(CopyIcon, {})
1037
+ }
1038
+ )
1039
+ ] }) }),
1040
+ tab === "diff" && showDiff && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-inspect-panel", children: /* @__PURE__ */ jsxRuntime.jsx(DiffTable, { lines: diff.lines }) }),
1041
+ tab === "metadata" && showMetadata && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-inspect-panel", children: /* @__PURE__ */ jsxRuntime.jsx(MetadataTable, { rows: metadataRows }) })
1042
+ ]
1043
+ }
1044
+ ) }) }),
1045
+ document.body
1046
+ );
1047
+ }
1048
+ function DiffTable({ lines }) {
1049
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-diff-wrap", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "audit-trail-diff-table", children: [
1050
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1051
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "Before" }),
1052
+ /* @__PURE__ */ jsxRuntime.jsx("th", { children: "After" })
1053
+ ] }) }),
1054
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: lines.map((line, idx) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1055
+ /* @__PURE__ */ jsxRuntime.jsx(
1056
+ "td",
1057
+ {
1058
+ className: `audit-trail-diff-cell audit-trail-diff-before-${line.beforeKind || "blank"}`,
1059
+ children: /* @__PURE__ */ jsxRuntime.jsx("pre", { children: line.before })
1060
+ }
1061
+ ),
1062
+ /* @__PURE__ */ jsxRuntime.jsx(
1063
+ "td",
1064
+ {
1065
+ className: `audit-trail-diff-cell audit-trail-diff-after-${line.afterKind || "blank"}`,
1066
+ children: /* @__PURE__ */ jsxRuntime.jsx("pre", { children: line.after })
1067
+ }
1068
+ )
1069
+ ] }, idx)) })
1070
+ ] }) });
1071
+ }
1072
+ function CopyIcon() {
1073
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1074
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
1075
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
1076
+ ] });
1077
+ }
1078
+ function CheckIcon() {
1079
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) });
1080
+ }
1081
+ function formatHeaderTimestamp(rfc3339) {
1082
+ if (!rfc3339) return "-";
1083
+ const d = new Date(rfc3339);
1084
+ if (Number.isNaN(d.getTime())) return rfc3339;
1085
+ const month = MONTHS[d.getUTCMonth()] ?? "";
1086
+ const day = d.getUTCDate();
1087
+ const year = d.getUTCFullYear();
1088
+ const hh = String(d.getUTCHours()).padStart(2, "0");
1089
+ const mm = String(d.getUTCMinutes()).padStart(2, "0");
1090
+ const ss = String(d.getUTCSeconds()).padStart(2, "0");
1091
+ const ms = String(d.getUTCMilliseconds()).padStart(3, "0");
1092
+ return `${month} ${day}, ${year} ${hh}:${mm}:${ss}.${ms} UTC`;
1093
+ }
1094
+ var MONTHS = [
1095
+ "Jan",
1096
+ "Feb",
1097
+ "Mar",
1098
+ "Apr",
1099
+ "May",
1100
+ "Jun",
1101
+ "Jul",
1102
+ "Aug",
1103
+ "Sep",
1104
+ "Oct",
1105
+ "Nov",
1106
+ "Dec"
1107
+ ];
1108
+ function inspectTabClass(active) {
1109
+ return active ? "audit-trail-inspect-tab audit-trail-inspect-tab-active" : "audit-trail-inspect-tab";
1110
+ }
1111
+ function highlightJSON(json) {
1112
+ const safe = escapeHTML(json);
1113
+ return safe.replace(
1114
+ /("(?:\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(?:\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
1115
+ (match) => {
1116
+ let cls = "audit-trail-json-num";
1117
+ if (match.startsWith('"')) {
1118
+ cls = /:$/.test(match) ? "audit-trail-json-key" : "audit-trail-json-str";
1119
+ } else if (/true|false/.test(match)) {
1120
+ cls = "audit-trail-json-bool";
1121
+ } else if (/null/.test(match)) {
1122
+ cls = "audit-trail-json-null";
1123
+ }
1124
+ return `<span class="${cls}">${match}</span>`;
1125
+ }
1126
+ );
1127
+ }
1128
+ function escapeHTML(s) {
1129
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1130
+ }
1131
+ function buildMetadataRows(metadata) {
1132
+ if (!metadata) return [];
1133
+ const keys = Object.keys(metadata);
1134
+ if (keys.length === 0) return [];
1135
+ keys.sort();
1136
+ return keys.map((k) => {
1137
+ const raw = metadata[k];
1138
+ return { key: k, value: renderMetadataValue(raw), type: classifyValue(raw) };
1139
+ });
1140
+ }
1141
+ function renderMetadataValue(v) {
1142
+ if (v === null || v === void 0) return "";
1143
+ if (typeof v === "string") return v;
1144
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
1145
+ try {
1146
+ return JSON.stringify(v);
1147
+ } catch {
1148
+ return String(v);
1149
+ }
1150
+ }
1151
+ function classifyValue(v) {
1152
+ if (v === null) return "null";
1153
+ if (Array.isArray(v)) return "array";
1154
+ if (typeof v === "object") return "object";
1155
+ return typeof v;
1156
+ }
1157
+ function MetadataTable({ rows }) {
1158
+ return /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "audit-trail-metadata-kv-table", children: [
1159
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1160
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "audit-trail-md-col-key", children: "Key" }),
1161
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "audit-trail-md-col-type", children: "Type" }),
1162
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "audit-trail-md-col-value", children: "Value" })
1163
+ ] }) }),
1164
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((r) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
1165
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "audit-trail-md-col-key", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: r.key }) }),
1166
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "audit-trail-md-col-type", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-muted", children: r.type }) }),
1167
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "audit-trail-md-col-value", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: r.value }) })
1168
+ ] }, r.key)) })
1169
+ ] });
1170
+ }
1171
+ function useRelativeTimeTick(intervalMs = componentsCore.RELATIVE_TIME_TICK_MS) {
1172
+ const [now, setNow] = react.useState(() => Date.now());
1173
+ react.useEffect(() => {
1174
+ const id = setInterval(() => setNow(Date.now()), intervalMs);
1175
+ return () => clearInterval(id);
1176
+ }, [intervalMs]);
1177
+ return now;
1178
+ }
1179
+ function EventRow({ event, columns, now, onClick }) {
1180
+ const interactive = Boolean(onClick);
1181
+ return /* @__PURE__ */ jsxRuntime.jsx(
1182
+ "tr",
1183
+ {
1184
+ className: "audit-trail-row",
1185
+ onClick: interactive ? () => onClick(event) : void 0,
1186
+ tabIndex: interactive ? 0 : void 0,
1187
+ onKeyDown: interactive ? (e) => {
1188
+ if (e.key === "Enter" || e.key === " ") {
1189
+ e.preventDefault();
1190
+ onClick(event);
1191
+ }
1192
+ } : void 0,
1193
+ children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx(
1194
+ "td",
1195
+ {
1196
+ className: `audit-trail-cell audit-trail-cell-${col}`,
1197
+ "data-label": componentsCore.COLUMN_LABELS[col] ?? col,
1198
+ children: renderCell(col, event, now)
1199
+ },
1200
+ col
1201
+ ))
1202
+ }
1203
+ );
1204
+ }
1205
+ function renderCell(column, event, now) {
1206
+ switch (column) {
1207
+ case "result":
1208
+ return renderResult(event.result);
1209
+ case "origin":
1210
+ return renderOrigin(event.origin);
1211
+ case "change":
1212
+ case "metadata":
1213
+ return renderPresence(getField(event, column));
1214
+ default: {
1215
+ const value = getField(event, column);
1216
+ if (value == null) return null;
1217
+ if (column === "occurred_at" && typeof value === "string") {
1218
+ return /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: value, title: value, children: componentsCore.formatTimeCell(value, now) });
1219
+ }
1220
+ if (typeof value === "string") return value;
1221
+ if (typeof value === "object") return summarizeObject(value);
1222
+ return String(value);
1223
+ }
1224
+ }
1225
+ }
1226
+ function getField(event, key) {
1227
+ return event[key];
1228
+ }
1229
+ function renderResult(value) {
1230
+ if (value == null || typeof value !== "object") return "-";
1231
+ const obj = value;
1232
+ const status = typeof obj.status === "string" ? obj.status : "";
1233
+ if (!status) return "-";
1234
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: `audit-trail-status audit-trail-status-${cssToken(status)}`, children: status });
1235
+ }
1236
+ function renderOrigin(value) {
1237
+ if (value == null || typeof value !== "object") return "-";
1238
+ const obj = value;
1239
+ for (const key of ["ip", "hostname", "host"]) {
1240
+ const v = obj[key];
1241
+ if (typeof v === "string" && v.length > 0) return v;
1242
+ }
1243
+ return summarizeObject(obj);
1244
+ }
1245
+ function renderPresence(value) {
1246
+ if (value == null) return "-";
1247
+ if (typeof value === "object") {
1248
+ const keys = Object.keys(value);
1249
+ if (keys.length === 0) return "-";
1250
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-cell-presence", children: "View" });
1251
+ }
1252
+ if (typeof value === "string" && value.length > 0) {
1253
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-cell-presence", children: "View" });
1254
+ }
1255
+ return "-";
1256
+ }
1257
+ function summarizeObject(value) {
1258
+ const obj = value;
1259
+ for (const key of ["name", "email", "id", "type"]) {
1260
+ const v = obj[key];
1261
+ if (typeof v === "string" && v.length > 0) return v;
1262
+ }
1263
+ const keys = Object.keys(obj);
1264
+ if (keys.length === 0) return "-";
1265
+ return `{${keys.length} ${keys.length === 1 ? "field" : "fields"}}`;
1266
+ }
1267
+ function cssToken(s) {
1268
+ return s.toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
1269
+ }
1270
+ function EventTable({ events, visibleColumns, onRowClick }) {
1271
+ const now = useRelativeTimeTick();
1272
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-table-wrap", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "audit-trail-table", children: [
1273
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: visibleColumns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", className: `audit-trail-th audit-trail-th-${col}`, children: componentsCore.COLUMN_LABELS[col] ?? col }, col)) }) }),
1274
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: events.map((event) => /* @__PURE__ */ jsxRuntime.jsx(
1275
+ EventRow,
1276
+ {
1277
+ event,
1278
+ columns: visibleColumns,
1279
+ now,
1280
+ onClick: onRowClick
1281
+ },
1282
+ event.id
1283
+ )) })
1284
+ ] }) });
1285
+ }
1286
+ function ExportModal({ open, onClose, onDownload }) {
1287
+ const [format, setFormat] = react.useState(null);
1288
+ const [status, setStatus] = react.useState("idle");
1289
+ const [errMsg, setErrMsg] = react.useState(null);
1290
+ react.useEffect(() => {
1291
+ if (!open) {
1292
+ setFormat(null);
1293
+ setStatus("idle");
1294
+ setErrMsg(null);
1295
+ }
1296
+ }, [open]);
1297
+ react.useEffect(() => {
1298
+ if (!open) return;
1299
+ const onKey = (e) => {
1300
+ if (e.key === "Escape" && status !== "downloading") onClose();
1301
+ };
1302
+ document.addEventListener("keydown", onKey);
1303
+ return () => document.removeEventListener("keydown", onKey);
1304
+ }, [open, status, onClose]);
1305
+ if (!open) return null;
1306
+ const handleDownload = async () => {
1307
+ if (!format) return;
1308
+ setStatus("downloading");
1309
+ setErrMsg(null);
1310
+ try {
1311
+ await onDownload(format);
1312
+ onClose();
1313
+ } catch (err) {
1314
+ setStatus("error");
1315
+ setErrMsg(err instanceof Error ? err.message : "Export failed.");
1316
+ }
1317
+ };
1318
+ const onBackdropClick = (e) => {
1319
+ if (e.target !== e.currentTarget) return;
1320
+ if (status === "downloading") return;
1321
+ onClose();
1322
+ };
1323
+ return /* @__PURE__ */ jsxRuntime.jsx(
1324
+ "div",
1325
+ {
1326
+ className: "audit-trail-export-backdrop",
1327
+ role: "presentation",
1328
+ onClick: onBackdropClick,
1329
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1330
+ "div",
1331
+ {
1332
+ className: "audit-trail-export-modal",
1333
+ role: "dialog",
1334
+ "aria-modal": "true",
1335
+ "aria-labelledby": "audit-trail-export-title",
1336
+ children: [
1337
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: "audit-trail-export-title", className: "audit-trail-export-title", children: "Export events" }),
1338
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "audit-trail-export-blurb", children: "The current filters and time range are applied. Capped at 100,000 rows - narrow the filters or time window if you hit it." }),
1339
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-export-options", children: [
1340
+ /* @__PURE__ */ jsxRuntime.jsx(
1341
+ FormatCard,
1342
+ {
1343
+ format: "csv",
1344
+ selected: format === "csv",
1345
+ onSelect: () => setFormat("csv"),
1346
+ title: "CSV",
1347
+ body: "Spreadsheet-friendly. Common fields are columns; nested fields (origin, metadata, change) are JSON in single columns."
1348
+ }
1349
+ ),
1350
+ /* @__PURE__ */ jsxRuntime.jsx(
1351
+ FormatCard,
1352
+ {
1353
+ format: "json",
1354
+ selected: format === "json",
1355
+ onSelect: () => setFormat("json"),
1356
+ title: "JSON",
1357
+ body: "Full event objects preserving nested structure. Same shape the API returns."
1358
+ }
1359
+ )
1360
+ ] }),
1361
+ status === "error" && errMsg && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-export-error", role: "alert", children: errMsg }),
1362
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-export-actions", children: [
1363
+ /* @__PURE__ */ jsxRuntime.jsx(
1364
+ "button",
1365
+ {
1366
+ type: "button",
1367
+ className: "audit-trail-button",
1368
+ onClick: handleDownload,
1369
+ disabled: !format || status === "downloading",
1370
+ children: status === "downloading" ? "Downloading\u2026" : "Download"
1371
+ }
1372
+ ),
1373
+ /* @__PURE__ */ jsxRuntime.jsx(
1374
+ "button",
1375
+ {
1376
+ type: "button",
1377
+ className: "audit-trail-button audit-trail-button-secondary",
1378
+ onClick: onClose,
1379
+ disabled: status === "downloading",
1380
+ children: "Cancel"
1381
+ }
1382
+ )
1383
+ ] })
1384
+ ]
1385
+ }
1386
+ )
1387
+ }
1388
+ );
1389
+ }
1390
+ function FormatCard({ format, selected, onSelect, title, body }) {
1391
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1392
+ "button",
1393
+ {
1394
+ type: "button",
1395
+ role: "radio",
1396
+ "aria-checked": selected,
1397
+ className: selected ? "audit-trail-export-card audit-trail-export-card-selected" : "audit-trail-export-card",
1398
+ onClick: onSelect,
1399
+ "data-format": format,
1400
+ children: [
1401
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-export-card-title", children: title }),
1402
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-export-card-body", children: body })
1403
+ ]
1404
+ }
1405
+ );
1406
+ }
1407
+ function FiltersToggle({ open, onOpenChange, activeCount }) {
1408
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1409
+ "button",
1410
+ {
1411
+ type: "button",
1412
+ className: "audit-trail-filter-toggle",
1413
+ "aria-expanded": open,
1414
+ onClick: () => onOpenChange(!open),
1415
+ children: [
1416
+ "Search",
1417
+ activeCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-filter-toggle-badge", children: activeCount }),
1418
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-filter-toggle-caret", "aria-hidden": "true", children: open ? "\u25B4" : "\u25BE" })
1419
+ ]
1420
+ }
1421
+ );
1422
+ }
1423
+ function LiveIndicator({ active }) {
1424
+ if (!active) return null;
1425
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "audit-trail-live", "aria-label": "Live updates", title: "Live updates", children: [
1426
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-live-dot", "aria-hidden": "true" }),
1427
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Live" })
1428
+ ] });
1429
+ }
1430
+ function useClaims(token) {
1431
+ return react.useMemo(() => componentsCore.parseClaims(token), [token]);
1432
+ }
1433
+ var NOOP_UNSUB = () => {
1434
+ };
1435
+ function useDistinctValues(opts) {
1436
+ const [store, setStore] = react.useState(null);
1437
+ react.useEffect(() => {
1438
+ if (!opts.token) {
1439
+ setStore(null);
1440
+ return;
1441
+ }
1442
+ const s = componentsCore.createDistinctValuesStore({
1443
+ apiBase: opts.apiBase,
1444
+ token: opts.token,
1445
+ tokenEndpoint: opts.tokenEndpoint,
1446
+ onTokenExpired: opts.onTokenExpired
1447
+ });
1448
+ setStore(s);
1449
+ return () => {
1450
+ s.dispose();
1451
+ };
1452
+ }, [opts.token, opts.apiBase]);
1453
+ const subscribe = react.useMemo(
1454
+ () => (listener) => store?.subscribe(listener) ?? NOOP_UNSUB,
1455
+ [store]
1456
+ );
1457
+ const getSnapshot = () => store?.getSnapshot() ?? componentsCore.EMPTY_DISTINCT_VALUES;
1458
+ return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1459
+ }
1460
+ var EMPTY_STATE = {
1461
+ events: [],
1462
+ nextCursor: null,
1463
+ status: "loading",
1464
+ error: null
1465
+ };
1466
+ var NOOP_UNSUB2 = () => {
1467
+ };
1468
+ function useEvents(opts) {
1469
+ const [store, setStore] = react.useState(null);
1470
+ react.useEffect(() => {
1471
+ if (!opts.token) {
1472
+ setStore(null);
1473
+ return;
1474
+ }
1475
+ const s = componentsCore.createEventsStore({
1476
+ apiBase: opts.apiBase,
1477
+ token: opts.token,
1478
+ pageSize: opts.pageSize,
1479
+ pollInterval: opts.pollInterval,
1480
+ tokenEndpoint: opts.tokenEndpoint,
1481
+ onTokenExpired: opts.onTokenExpired,
1482
+ onError: opts.onError,
1483
+ since: opts.since,
1484
+ before: opts.before,
1485
+ action: opts.action,
1486
+ actor: opts.actor,
1487
+ actorType: opts.actorType,
1488
+ tenantId: opts.tenantId,
1489
+ targetType: opts.targetType,
1490
+ targetId: opts.targetId,
1491
+ resultStatus: opts.resultStatus,
1492
+ originIP: opts.originIP,
1493
+ q: opts.q
1494
+ });
1495
+ setStore(s);
1496
+ return () => {
1497
+ s.dispose();
1498
+ };
1499
+ }, [
1500
+ opts.token,
1501
+ opts.apiBase,
1502
+ opts.pageSize,
1503
+ opts.pollInterval,
1504
+ opts.since,
1505
+ opts.before,
1506
+ opts.action,
1507
+ opts.actor,
1508
+ opts.actorType,
1509
+ opts.tenantId,
1510
+ opts.targetType,
1511
+ opts.targetId,
1512
+ opts.resultStatus,
1513
+ opts.originIP,
1514
+ opts.q
1515
+ ]);
1516
+ const subscribe = react.useMemo(
1517
+ () => (listener) => store?.subscribe(listener) ?? NOOP_UNSUB2,
1518
+ [store]
1519
+ );
1520
+ const getSnapshot = () => store?.getSnapshot() ?? EMPTY_STATE;
1521
+ const state = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
1522
+ return {
1523
+ events: state.events,
1524
+ hasMore: state.nextCursor !== null,
1525
+ status: state.status,
1526
+ error: state.error,
1527
+ loadMore: () => store?.loadMore(),
1528
+ refresh: () => store?.refresh()
1529
+ };
1530
+ }
1531
+ var DEFAULT_API_BASE = "https://api.everscribe.io/v1/embed";
1532
+ var DEFAULT_PAGE_SIZE = 25;
1533
+ var DEFAULT_POLL_INTERVAL_MS = 5e3;
1534
+ var DEFAULT_VISIBLE_COLUMNS = [
1535
+ "occurred_at",
1536
+ "action",
1537
+ "actor",
1538
+ "target",
1539
+ "tenant_id",
1540
+ "result"
1541
+ ];
1542
+ function AuditTrail(props) {
1543
+ const [bootstrap, setBootstrap] = react.useState(
1544
+ () => props.token ? { phase: "ready", token: props.token } : { phase: "idle" }
1545
+ );
1546
+ const [retryCount, setRetryCount] = react.useState(0);
1547
+ react.useEffect(() => {
1548
+ if (props.token) setBootstrap({ phase: "ready", token: props.token });
1549
+ }, [props.token]);
1550
+ const propsRef = react.useRef(props);
1551
+ propsRef.current = props;
1552
+ react.useEffect(() => {
1553
+ if (propsRef.current.token) return;
1554
+ if (!propsRef.current.tokenEndpoint && !propsRef.current.onTokenExpired) {
1555
+ setBootstrap({ phase: "error", reason: "config" });
1556
+ return;
1557
+ }
1558
+ setBootstrap({ phase: "loading" });
1559
+ let cancelled = false;
1560
+ void (async () => {
1561
+ const t = await componentsCore.fetchTokenViaOpts({
1562
+ tokenEndpoint: propsRef.current.tokenEndpoint,
1563
+ onTokenExpired: propsRef.current.onTokenExpired
1564
+ });
1565
+ if (cancelled) return;
1566
+ if (!t) {
1567
+ setBootstrap({ phase: "error", reason: "fetch" });
1568
+ return;
1569
+ }
1570
+ setBootstrap({ phase: "ready", token: t });
1571
+ })();
1572
+ return () => {
1573
+ cancelled = true;
1574
+ };
1575
+ }, [props.token, retryCount]);
1576
+ const activeToken = bootstrap.phase === "ready" ? bootstrap.token : null;
1577
+ const claims = useClaims(activeToken);
1578
+ const [selected, setSelected] = react.useState(null);
1579
+ const [filters, setFilters] = react.useState(() => ({
1580
+ range: props.defaultTimeRange ?? "all"
1581
+ }));
1582
+ const [filtersOpen, setFiltersOpen] = react.useState(false);
1583
+ const [exportOpen, setExportOpen] = react.useState(false);
1584
+ const pollIntervalMs = props.pollInterval ?? DEFAULT_POLL_INTERVAL_MS;
1585
+ const availableColumns = react.useMemo(() => {
1586
+ if (claims?.columns && claims.columns.length > 0) return claims.columns;
1587
+ return componentsCore.ALL_COLUMNS;
1588
+ }, [claims]);
1589
+ const [visibleSet, setVisibleSet] = react.useState(() => {
1590
+ if (claims?.columns && claims.columns.length > 0) return new Set(claims.columns);
1591
+ return new Set(DEFAULT_VISIBLE_COLUMNS);
1592
+ });
1593
+ const restoredKeyRef = react.useRef(null);
1594
+ const claimsKey = claims?.sub ? `${claims.sub}:${claims.tenant_id || "_"}` : null;
1595
+ react.useEffect(() => {
1596
+ if (!claimsKey) return;
1597
+ if (restoredKeyRef.current === claimsKey) return;
1598
+ restoredKeyRef.current = claimsKey;
1599
+ try {
1600
+ const raw = globalThis.localStorage?.getItem(`audit-trail:cols:${claimsKey}`);
1601
+ if (raw) {
1602
+ const hidden = JSON.parse(raw);
1603
+ if (Array.isArray(hidden)) {
1604
+ setVisibleSet((prev) => {
1605
+ const next = new Set(prev);
1606
+ for (const c of hidden) {
1607
+ if (typeof c === "string") next.delete(c);
1608
+ }
1609
+ return next;
1610
+ });
1611
+ }
1612
+ }
1613
+ } catch {
1614
+ }
1615
+ try {
1616
+ const raw = globalThis.localStorage?.getItem(`audit-trail:filters:${claimsKey}`);
1617
+ if (raw) {
1618
+ const parsed = JSON.parse(raw);
1619
+ if (parsed && typeof parsed === "object") {
1620
+ const range = parsed.range;
1621
+ if (range === "24h" || range === "7d" || range === "30d" || range === "custom" || range === "all") {
1622
+ setFilters(parsed);
1623
+ }
1624
+ }
1625
+ }
1626
+ } catch {
1627
+ }
1628
+ }, [claimsKey]);
1629
+ react.useEffect(() => {
1630
+ if (!claimsKey || restoredKeyRef.current !== claimsKey) return;
1631
+ try {
1632
+ const hidden = availableColumns.filter((c) => !visibleSet.has(c));
1633
+ globalThis.localStorage?.setItem(`audit-trail:cols:${claimsKey}`, JSON.stringify(hidden));
1634
+ } catch {
1635
+ }
1636
+ }, [visibleSet, claimsKey, availableColumns]);
1637
+ react.useEffect(() => {
1638
+ if (!claimsKey || restoredKeyRef.current !== claimsKey) return;
1639
+ try {
1640
+ globalThis.localStorage?.setItem(`audit-trail:filters:${claimsKey}`, JSON.stringify(filters));
1641
+ } catch {
1642
+ }
1643
+ }, [filters, claimsKey]);
1644
+ const visibleColumns = react.useMemo(
1645
+ () => availableColumns.filter((c) => visibleSet.has(c)),
1646
+ [availableColumns, visibleSet]
1647
+ );
1648
+ const toggleColumn = (col) => {
1649
+ setVisibleSet((prev) => {
1650
+ const next = new Set(prev);
1651
+ if (next.has(col)) next.delete(col);
1652
+ else next.add(col);
1653
+ return next;
1654
+ });
1655
+ };
1656
+ const { since: filterSince, before: filterBefore } = react.useMemo(
1657
+ () => resolveTimeBounds(filters),
1658
+ [filters]
1659
+ );
1660
+ const livePollActive = pollIntervalMs > 0 && !filterBefore;
1661
+ const distinct = useDistinctValues({
1662
+ apiBase: props.apiBase ?? DEFAULT_API_BASE,
1663
+ token: activeToken,
1664
+ tokenEndpoint: props.tokenEndpoint,
1665
+ onTokenExpired: props.onTokenExpired
1666
+ });
1667
+ const { events, hasMore, status, error, loadMore, refresh } = useEvents({
1668
+ apiBase: props.apiBase ?? DEFAULT_API_BASE,
1669
+ token: activeToken,
1670
+ pageSize: props.pageSize ?? DEFAULT_PAGE_SIZE,
1671
+ pollInterval: pollIntervalMs,
1672
+ tokenEndpoint: props.tokenEndpoint,
1673
+ onTokenExpired: props.onTokenExpired,
1674
+ onError: props.onError,
1675
+ since: filterSince,
1676
+ before: filterBefore,
1677
+ action: filters.action,
1678
+ actor: filters.actor,
1679
+ actorType: filters.actorType,
1680
+ tenantId: filters.tenantId,
1681
+ targetType: filters.targetType,
1682
+ targetId: filters.targetId,
1683
+ resultStatus: filters.resultStatus,
1684
+ originIP: filters.originIP,
1685
+ q: filters.q
1686
+ });
1687
+ const handleExportDownload = react.useCallback(
1688
+ async (format) => {
1689
+ if (!activeToken) throw new Error("Not authenticated.");
1690
+ const apiBase = props.apiBase ?? DEFAULT_API_BASE;
1691
+ const params = {
1692
+ format,
1693
+ since: filterSince,
1694
+ before: filterBefore,
1695
+ action: filters.action,
1696
+ actor: filters.actor,
1697
+ actorType: filters.actorType,
1698
+ tenantId: filters.tenantId,
1699
+ targetType: filters.targetType,
1700
+ targetId: filters.targetId,
1701
+ resultStatus: filters.resultStatus,
1702
+ originIP: filters.originIP,
1703
+ q: filters.q
1704
+ };
1705
+ const run = (token) => componentsCore.exportEvents({ apiBase, token, params });
1706
+ let result;
1707
+ try {
1708
+ result = await run(activeToken);
1709
+ } catch (err) {
1710
+ if (err instanceof componentsCore.EmbedError && err.kind === "unauthorized") {
1711
+ const refreshed = await componentsCore.fetchTokenViaOpts({
1712
+ tokenEndpoint: props.tokenEndpoint,
1713
+ onTokenExpired: props.onTokenExpired
1714
+ });
1715
+ if (!refreshed) throw new Error("Authentication failed.");
1716
+ result = await run(refreshed);
1717
+ } else if (err instanceof componentsCore.EmbedError) {
1718
+ throw new Error(exportErrorMessage(err));
1719
+ } else {
1720
+ throw err;
1721
+ }
1722
+ }
1723
+ triggerBrowserDownload(result.blob, result.filename);
1724
+ },
1725
+ [
1726
+ activeToken,
1727
+ props.apiBase,
1728
+ props.tokenEndpoint,
1729
+ props.onTokenExpired,
1730
+ filterSince,
1731
+ filterBefore,
1732
+ filters.action,
1733
+ filters.actor,
1734
+ filters.actorType,
1735
+ filters.tenantId,
1736
+ filters.targetType,
1737
+ filters.targetId,
1738
+ filters.resultStatus,
1739
+ filters.originIP,
1740
+ filters.q
1741
+ ]
1742
+ );
1743
+ const rootClassName = [
1744
+ "audit-trail-root",
1745
+ `audit-trail-theme-${props.theme ?? "light"}`,
1746
+ props.className
1747
+ ].filter(Boolean).join(" ");
1748
+ if (bootstrap.phase === "error" && bootstrap.reason === "config") {
1749
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: rootClassName, style: props.style, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-state audit-trail-state-error", children: [
1750
+ "Configure ",
1751
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "token" }),
1752
+ ", ",
1753
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "tokenEndpoint" }),
1754
+ ", or",
1755
+ " ",
1756
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "onTokenExpired" }),
1757
+ "."
1758
+ ] }) });
1759
+ }
1760
+ if (bootstrap.phase === "error" && bootstrap.reason === "fetch") {
1761
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: rootClassName, style: props.style, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-state audit-trail-state-error", children: [
1762
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Couldn\u2019t fetch token." }),
1763
+ /* @__PURE__ */ jsxRuntime.jsx(
1764
+ "button",
1765
+ {
1766
+ type: "button",
1767
+ className: "audit-trail-button",
1768
+ onClick: () => setRetryCount((c) => c + 1),
1769
+ children: "Retry"
1770
+ }
1771
+ )
1772
+ ] }) });
1773
+ }
1774
+ if (bootstrap.phase !== "ready") {
1775
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: rootClassName, style: props.style, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-state audit-trail-state-loading", children: "Loading\u2026" }) });
1776
+ }
1777
+ if (claims === null) {
1778
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: rootClassName, style: props.style, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-state audit-trail-state-error", children: "Invalid token." }) });
1779
+ }
1780
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rootClassName, style: props.style, children: [
1781
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-toolbar", children: [
1782
+ /* @__PURE__ */ jsxRuntime.jsx(LiveIndicator, { active: livePollActive }),
1783
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "audit-trail-toolbar-spacer" }),
1784
+ /* @__PURE__ */ jsxRuntime.jsx(
1785
+ FiltersToggle,
1786
+ {
1787
+ open: filtersOpen,
1788
+ onOpenChange: setFiltersOpen,
1789
+ activeCount: countActiveColumnFilters(filters) + (filters.q ? parseQClauses(filters.q).length : 0)
1790
+ }
1791
+ ),
1792
+ /* @__PURE__ */ jsxRuntime.jsx(
1793
+ "button",
1794
+ {
1795
+ type: "button",
1796
+ className: "audit-trail-button",
1797
+ onClick: () => setExportOpen(true),
1798
+ children: "Export"
1799
+ }
1800
+ ),
1801
+ /* @__PURE__ */ jsxRuntime.jsx(
1802
+ ColumnPicker,
1803
+ {
1804
+ available: availableColumns,
1805
+ visible: visibleSet,
1806
+ onToggle: toggleColumn
1807
+ }
1808
+ )
1809
+ ] }),
1810
+ /* @__PURE__ */ jsxRuntime.jsx(ActiveFilterChips, { value: filters, onChange: setFilters }),
1811
+ filtersOpen && /* @__PURE__ */ jsxRuntime.jsx(
1812
+ FiltersPanel,
1813
+ {
1814
+ value: filters,
1815
+ onChange: setFilters,
1816
+ distinct,
1817
+ claims,
1818
+ apiBase: props.apiBase ?? DEFAULT_API_BASE,
1819
+ token: activeToken,
1820
+ tokenEndpoint: props.tokenEndpoint,
1821
+ onTokenExpired: props.onTokenExpired,
1822
+ onApplied: () => setFiltersOpen(false)
1823
+ }
1824
+ ),
1825
+ status === "loading" && events.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-state audit-trail-state-loading", children: "Loading\u2026" }),
1826
+ status === "expired" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-state audit-trail-state-error", children: "Session expired." }),
1827
+ status === "error" && error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "audit-trail-state audit-trail-state-error", children: [
1828
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: errorMessage(error) }),
1829
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "audit-trail-button", onClick: refresh, children: "Retry" })
1830
+ ] }),
1831
+ status === "ok" && events.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-state audit-trail-state-empty", children: "No events match." }),
1832
+ events.length > 0 && visibleColumns.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "audit-trail-state audit-trail-state-empty", children: "No columns selected." }),
1833
+ events.length > 0 && visibleColumns.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1834
+ /* @__PURE__ */ jsxRuntime.jsx(
1835
+ EventTable,
1836
+ {
1837
+ events,
1838
+ visibleColumns,
1839
+ onRowClick: setSelected
1840
+ }
1841
+ ),
1842
+ hasMore && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "audit-trail-button audit-trail-load-more", onClick: loadMore, children: "Load more" })
1843
+ ] }),
1844
+ /* @__PURE__ */ jsxRuntime.jsx(
1845
+ ExportModal,
1846
+ {
1847
+ open: exportOpen,
1848
+ onClose: () => setExportOpen(false),
1849
+ onDownload: handleExportDownload
1850
+ }
1851
+ ),
1852
+ selected && /* @__PURE__ */ jsxRuntime.jsx(
1853
+ EventDetail,
1854
+ {
1855
+ event: selected,
1856
+ onClose: () => setSelected(null),
1857
+ theme: props.theme ?? "light"
1858
+ },
1859
+ selected.id
1860
+ )
1861
+ ] });
1862
+ }
1863
+ function resolveTimeBounds(filters) {
1864
+ switch (filters.range) {
1865
+ case "24h":
1866
+ return { since: relativeIso(24 * 60 * 60 * 1e3) };
1867
+ case "7d":
1868
+ return { since: relativeIso(7 * 24 * 60 * 60 * 1e3) };
1869
+ case "30d":
1870
+ return { since: relativeIso(30 * 24 * 60 * 60 * 1e3) };
1871
+ case "custom":
1872
+ return {
1873
+ since: localToIso(filters.since),
1874
+ before: localToIso(filters.before)
1875
+ };
1876
+ case "all":
1877
+ default:
1878
+ return {};
1879
+ }
1880
+ }
1881
+ function relativeIso(ms) {
1882
+ return new Date(Date.now() - ms).toISOString();
1883
+ }
1884
+ function localToIso(value) {
1885
+ if (!value) return void 0;
1886
+ const d = new Date(value);
1887
+ if (Number.isNaN(d.getTime())) return void 0;
1888
+ return d.toISOString();
1889
+ }
1890
+ function triggerBrowserDownload(blob, filename) {
1891
+ const url = URL.createObjectURL(blob);
1892
+ const a = document.createElement("a");
1893
+ a.href = url;
1894
+ a.download = filename;
1895
+ document.body.appendChild(a);
1896
+ a.click();
1897
+ document.body.removeChild(a);
1898
+ setTimeout(() => URL.revokeObjectURL(url), 0);
1899
+ }
1900
+ function exportErrorMessage(err) {
1901
+ switch (err.kind) {
1902
+ case "rate_limited":
1903
+ return "Too many requests. Try again in a moment.";
1904
+ case "bad_request":
1905
+ return err.message || "Bad request.";
1906
+ case "server":
1907
+ return "Server error. Please try again.";
1908
+ case "network":
1909
+ return "Network error. Check your connection.";
1910
+ case "not_found":
1911
+ return "Not found.";
1912
+ case "unauthorized":
1913
+ return "Authentication failed.";
1914
+ }
1915
+ }
1916
+ function errorMessage(err) {
1917
+ switch (err.kind) {
1918
+ case "unauthorized":
1919
+ return "Authentication failed.";
1920
+ case "not_found":
1921
+ return "Not found.";
1922
+ case "rate_limited":
1923
+ return "Too many requests. Please slow down.";
1924
+ case "bad_request":
1925
+ return err.message || "Bad request.";
1926
+ case "server":
1927
+ return "Server error. Please try again.";
1928
+ case "network":
1929
+ return "Network error. Check your connection.";
1930
+ }
1931
+ }
1932
+
1933
+ exports.AuditTrail = AuditTrail;
1934
+ //# sourceMappingURL=index.cjs.map
1935
+ //# sourceMappingURL=index.cjs.map