@fonderie/react-admin-screens 0.3.0 → 0.5.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/README.md CHANGED
@@ -14,10 +14,10 @@ Status: **experimental** (0.x).
14
14
  | Money | Catalog · Subscriber | what am I selling (configured vs stored) · what is this subscriber on, their wallet, ledger, a manual grant (`billingClient`) |
15
15
  | Settings | Config & secrets | the `@fonderie/config` admin screens, as a sub-page |
16
16
  | Messaging | Templates | the `@fonderie/courier` admin screens, as a sub-page |
17
- | Activity | Admin log · Access | who did what · who can be here |
17
+ | Activity | Audit · Admin log · Access | what happened, every workspace (`auditClient`) · who did what through here · who can be here, and issue or revoke scoped tokens |
18
18
 
19
19
  ```ts
20
- import { AdminClient, AuthAdminClient, BillingAdminClient, ConfigAdminClient, CourierAdminClient } from '@fonderie/client';
20
+ import { AdminClient, AuditAdminClient, AuthAdminClient, BillingAdminClient, ConfigAdminClient, CourierAdminClient } from '@fonderie/client';
21
21
  import { AdminShell } from '@fonderie/react-admin-screens';
22
22
 
23
23
  const base = { baseUrl: 'https://api.example.com', adminToken, actor: 'louis' };
@@ -27,16 +27,24 @@ const configClient = new ConfigAdminClient({ ...base, prefix: '/_admin' });
27
27
  const courierClient = new CourierAdminClient({ ...base, prefix: '/_admin' });
28
28
  const authClient = new AuthAdminClient(base); // @fonderie/auth ≥ 7.8
29
29
  const billingClient = new BillingAdminClient(base); // @fonderie/billing ≥ 9.10
30
+ const auditClient = new AuditAdminClient(base); // @fonderie/audit ≥ 5.2
30
31
  ```
31
32
 
32
33
  ```tsx
33
- <AdminShell client={client} configClient={configClient} courierClient={courierClient} authClient={authClient} billingClient={billingClient} />
34
+ <AdminShell client={client} configClient={configClient} courierClient={courierClient} authClient={authClient} billingClient={billingClient} auditClient={auditClient} />
34
35
  ```
35
36
 
36
37
  Pass `page` and `onNavigate` to own the URL (one route per page); omit both
37
38
  and the shell keeps the page itself. Settings and Messaging appear only when
38
39
  their client is given.
39
40
 
41
+ **Give the shell a `read` token, not the root one.** With `@fonderie/admin`
42
+ ≥ 0.6 the configured `adminToken` is the root — every scope, including the
43
+ plaintext secret reveal, and the only credential that can mint or revoke.
44
+ Issue a scoped one for the dashboard (`fonderie admin token issue dashboard
45
+ --scopes read`, or the Access page itself) and hand the shell that. The
46
+ Access page then shows issuing as unavailable, which is the point.
47
+
40
48
  Nothing here stores the token: the app constructs the clients and decides
41
49
  where the token lives. Every request through the shell lands in the admin
42
50
  log, including the ones the token refuses.
package/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(index_exports, {
23
23
  AdminLogScreen: () => AdminLogScreen,
24
24
  AdminShell: () => AdminShell,
25
25
  AttentionScreen: () => AttentionScreen,
26
+ AuditScreen: () => AuditScreen,
26
27
  CatalogScreen: () => CatalogScreen,
27
28
  ConfigScreen: () => ConfigScreen,
28
29
  DoctorScreen: () => DoctorScreen,
@@ -37,7 +38,7 @@ module.exports = __toCommonJS(index_exports);
37
38
  // src/screens/AdminShell.tsx
38
39
  var import_react_config_admin_screens = require("@fonderie/react-config-admin-screens");
39
40
  var import_react_courier_admin_screens = require("@fonderie/react-courier-admin-screens");
40
- var import_react3 = require("react");
41
+ var import_react5 = require("react");
41
42
 
42
43
  // src/styles.ts
43
44
  var styles = {
@@ -345,34 +346,146 @@ function RoutesScreen({ client }) {
345
346
 
346
347
  // src/screens/TokensScreen.tsx
347
348
  var import_react_admin7 = require("@fonderie/react-admin");
349
+ var import_react = require("react");
348
350
  var import_jsx_runtime7 = require("react/jsx-runtime");
351
+ var ALL = ["read", "write", "secrets"];
349
352
  function TokensScreen({ client }) {
350
- const { report, isLoading, error } = (0, import_react_admin7.useAdminTokens)(client);
353
+ const { report, isLoading, error, issue, revoke } = (0, import_react_admin7.useAdminTokens)(client);
354
+ const [name, setName] = (0, import_react.useState)("");
355
+ const [scopes, setScopes] = (0, import_react.useState)(["read"]);
356
+ const [days, setDays] = (0, import_react.useState)("");
357
+ const [minted, setMinted] = (0, import_react.useState)(null);
358
+ const toggle = (s) => setScopes((prev) => prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]);
351
359
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: styles.container, children: [
352
360
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { style: styles.title, children: "Access" }),
353
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : report ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
354
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Admin token" }),
355
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
356
- report.admin.ok ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.ok, children: "strong" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.bad, children: "weak" }),
357
- report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: styles.bad, children: p.message }, p.message))
358
- ] }),
359
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
360
- report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: styles.list, children: report.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { style: styles.row, children: [
361
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.mono, children: l.module }),
361
+ error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.error, role: "alert", children: error.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.explanation }) : null,
362
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Root token" }),
363
+ report ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
364
+ report.admin.ok ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.ok, children: "strong" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.bad, children: "weak" }),
365
+ report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: styles.bad, children: p.message }, p.message))
366
+ ] }) : null,
367
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Issued tokens" }),
368
+ report?.issued === null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "Issuing is off \u2014 give AdminModule a store and run its migrations." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
369
+ minted ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { style: styles.ok, children: [
370
+ "Copy this now \u2014 it is never shown again.",
362
371
  " ",
363
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
364
- ] }, l.module)) })
365
- ] }) : null
372
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.mono, children: minted.token }),
373
+ " (",
374
+ minted.name,
375
+ ")"
376
+ ] }) : null,
377
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
378
+ "form",
379
+ {
380
+ style: styles.toolbar,
381
+ onSubmit: (e) => {
382
+ e.preventDefault();
383
+ const d = Number(days);
384
+ void issue({
385
+ name: name.trim(),
386
+ scopes,
387
+ ...days && Number.isInteger(d) && d > 0 ? { expiresInDays: d } : {}
388
+ }).then((t) => {
389
+ setMinted({ name: t.name, token: t.token });
390
+ setName("");
391
+ setDays("");
392
+ }).catch(() => {
393
+ });
394
+ },
395
+ children: [
396
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
397
+ "input",
398
+ {
399
+ value: name,
400
+ onChange: (e) => setName(e.target.value),
401
+ placeholder: "name",
402
+ style: { ...styles.button, cursor: "text" },
403
+ "aria-label": "Name"
404
+ }
405
+ ),
406
+ ALL.map((s) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { style: { ...styles.badge, cursor: "pointer" }, children: [
407
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: scopes.includes(s), onChange: () => toggle(s) }),
408
+ " ",
409
+ s
410
+ ] }, s)),
411
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
412
+ "input",
413
+ {
414
+ value: days,
415
+ onChange: (e) => setDays(e.target.value),
416
+ placeholder: "days (optional)",
417
+ style: { ...styles.button, cursor: "text", width: 140 },
418
+ "aria-label": "Days"
419
+ }
420
+ ),
421
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
422
+ "button",
423
+ {
424
+ type: "submit",
425
+ style: styles.button,
426
+ disabled: !name.trim() || scopes.length === 0 || isLoading,
427
+ children: "Issue"
428
+ }
429
+ )
430
+ ]
431
+ }
432
+ ),
433
+ report?.issued && report.issued.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { style: styles.table, children: [
434
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
435
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Name" }),
436
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Scopes" }),
437
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Created" }),
438
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Expires" }),
439
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Last used" }),
440
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th })
441
+ ] }) }),
442
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: report.issued.map((t) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
443
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { style: styles.td, children: [
444
+ t.name,
445
+ t.revokedAt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.badge, children: " revoked" }) : null
446
+ ] }),
447
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t.scopes.join(", ") }),
448
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { style: { ...styles.td, ...styles.muted }, children: [
449
+ new Date(t.createdAt).toLocaleDateString(),
450
+ " \xB7 ",
451
+ t.createdBy
452
+ ] }),
453
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t.expiresAt ? new Date(t.expiresAt).toLocaleDateString() : "never" }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t.lastUsedAt ? new Date(t.lastUsedAt).toLocaleString() : "never" }),
455
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: styles.td, children: t.revokedAt ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
456
+ "button",
457
+ {
458
+ type: "button",
459
+ style: styles.button,
460
+ onClick: () => {
461
+ if (window.confirm(
462
+ `Revoke "${t.name}"? Anything using it stops working immediately.`
463
+ ))
464
+ void revoke(t.id).catch(() => {
465
+ });
466
+ },
467
+ children: "Revoke"
468
+ }
469
+ ) })
470
+ ] }, t.id)) })
471
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "None issued." })
472
+ ] }),
473
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
474
+ report && report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: styles.list, children: report?.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { style: styles.row, children: [
475
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.mono, children: l.module }),
476
+ " ",
477
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
478
+ ] }, l.module)) })
366
479
  ] });
367
480
  }
368
481
 
369
482
  // src/screens/UsersScreen.tsx
370
483
  var import_react_admin8 = require("@fonderie/react-admin");
371
- var import_react = require("react");
484
+ var import_react2 = require("react");
372
485
  var import_jsx_runtime8 = require("react/jsx-runtime");
373
486
  function UsersScreen({ client }) {
374
- const [input, setInput] = (0, import_react.useState)("");
375
- const [email, setEmail] = (0, import_react.useState)(void 0);
487
+ const [input, setInput] = (0, import_react2.useState)("");
488
+ const [email, setEmail] = (0, import_react2.useState)(void 0);
376
489
  const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_react_admin8.useAdminUser)(
377
490
  client,
378
491
  email ? { email } : {}
@@ -582,16 +695,16 @@ function CatalogScreen({ client }) {
582
695
 
583
696
  // src/screens/SubscriberScreen.tsx
584
697
  var import_react_admin10 = require("@fonderie/react-admin");
585
- var import_react2 = require("react");
698
+ var import_react3 = require("react");
586
699
  var import_jsx_runtime10 = require("react/jsx-runtime");
587
700
  function SubscriberScreen({ client }) {
588
- const [type, setType] = (0, import_react2.useState)("user");
589
- const [idInput, setIdInput] = (0, import_react2.useState)("");
590
- const [subscriber, setSubscriber] = (0, import_react2.useState)(null);
701
+ const [type, setType] = (0, import_react3.useState)("user");
702
+ const [idInput, setIdInput] = (0, import_react3.useState)("");
703
+ const [subscriber, setSubscriber] = (0, import_react3.useState)(null);
591
704
  const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin10.useAdminSubscriber)(client, subscriber, { limit: 20 });
592
- const [amount, setAmount] = (0, import_react2.useState)("");
593
- const [note, setNote] = (0, import_react2.useState)("");
594
- const [granted, setGranted] = (0, import_react2.useState)(null);
705
+ const [amount, setAmount] = (0, import_react3.useState)("");
706
+ const [note, setNote] = (0, import_react3.useState)("");
707
+ const [granted, setGranted] = (0, import_react3.useState)(null);
595
708
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.container, children: [
596
709
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { style: styles.title, children: "Subscriber" }),
597
710
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -754,8 +867,93 @@ function SubscriberScreen({ client }) {
754
867
  ] });
755
868
  }
756
869
 
757
- // src/screens/AdminShell.tsx
870
+ // src/screens/AuditScreen.tsx
871
+ var import_react_admin11 = require("@fonderie/react-admin");
872
+ var import_react4 = require("react");
758
873
  var import_jsx_runtime11 = require("react/jsx-runtime");
874
+ function AuditScreen({ client, pageSize = 50 }) {
875
+ const [draft, setDraft] = (0, import_react4.useState)({ workspaceId: "", type: "", actorId: "" });
876
+ const [filter, setFilter] = (0, import_react4.useState)(
877
+ {}
878
+ );
879
+ const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin11.useAdminAudit)(client, {
880
+ ...filter,
881
+ limit: pageSize
882
+ });
883
+ const field = (key, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
884
+ "input",
885
+ {
886
+ value: draft[key],
887
+ onChange: (e) => setDraft({ ...draft, [key]: e.target.value }),
888
+ placeholder,
889
+ style: { ...styles.button, cursor: "text" },
890
+ "aria-label": placeholder
891
+ }
892
+ );
893
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.container, children: [
894
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { style: styles.title, children: "Audit" }),
895
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
896
+ "form",
897
+ {
898
+ style: styles.toolbar,
899
+ onSubmit: (e) => {
900
+ e.preventDefault();
901
+ setFilter({
902
+ ...draft.workspaceId.trim() ? { workspaceId: draft.workspaceId.trim() } : {},
903
+ ...draft.type.trim() ? { type: draft.type.trim() } : {},
904
+ ...draft.actorId.trim() ? { actorId: draft.actorId.trim() } : {}
905
+ });
906
+ },
907
+ children: [
908
+ field("workspaceId", "workspace id (all if empty)"),
909
+ field("type", "event type"),
910
+ field("actorId", "actor id"),
911
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Filter" }),
912
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
913
+ "button",
914
+ {
915
+ type: "button",
916
+ style: styles.button,
917
+ onClick: () => void refresh(),
918
+ disabled: isLoading,
919
+ children: "Refresh"
920
+ }
921
+ )
922
+ ]
923
+ }
924
+ ),
925
+ error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
926
+ events.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.muted, children: "Nothing recorded for this filter." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { style: styles.table, children: [
927
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
928
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "When" }),
929
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Type" }),
930
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Workspace" }),
931
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Actor" }),
932
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Request" })
933
+ ] }) }),
934
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
935
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
936
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
937
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
938
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
939
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
940
+ ] }, e.id)) })
941
+ ] }),
942
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
943
+ hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
944
+ "button",
945
+ {
946
+ type: "button",
947
+ style: { ...styles.button, marginTop: 8 },
948
+ onClick: () => void loadMore(),
949
+ children: "Load more"
950
+ }
951
+ ) : null
952
+ ] });
953
+ }
954
+
955
+ // src/screens/AdminShell.tsx
956
+ var import_jsx_runtime12 = require("react/jsx-runtime");
759
957
  var NAV = [
760
958
  { group: "Today", items: [{ page: "attention", label: "Attention" }] },
761
959
  {
@@ -780,6 +978,7 @@ var NAV = [
780
978
  {
781
979
  group: "Activity",
782
980
  items: [
981
+ { page: "audit", label: "Audit", needs: "audit" },
783
982
  { page: "log", label: "Admin log" },
784
983
  { page: "tokens", label: "Access" }
785
984
  ]
@@ -791,59 +990,64 @@ function AdminShell({
791
990
  courierClient,
792
991
  authClient,
793
992
  billingClient,
993
+ auditClient,
794
994
  environment,
795
995
  page,
796
996
  onNavigate
797
997
  }) {
798
- const [own, setOwn] = (0, import_react3.useState)("attention");
998
+ const [own, setOwn] = (0, import_react5.useState)("attention");
799
999
  const current = page ?? own;
800
1000
  const go = (p) => {
801
1001
  if (onNavigate) onNavigate(p);
802
1002
  if (page === void 0) setOwn(p);
803
1003
  };
804
- const [editing, setEditing] = (0, import_react3.useState)(null);
1004
+ const [editing, setEditing] = (0, import_react5.useState)(null);
805
1005
  const has = {
806
1006
  config: Boolean(configClient),
807
1007
  courier: Boolean(courierClient),
808
1008
  auth: Boolean(authClient),
809
- billing: Boolean(billingClient)
1009
+ billing: Boolean(billingClient),
1010
+ audit: Boolean(auditClient)
810
1011
  };
811
1012
  let body;
812
1013
  switch (current) {
813
1014
  case "attention":
814
- body = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AttentionScreen, { client });
1015
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AttentionScreen, { client });
815
1016
  break;
816
1017
  case "modules":
817
- body = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ModulesScreen, { client });
1018
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ModulesScreen, { client });
818
1019
  break;
819
1020
  case "doctor":
820
- body = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(DoctorScreen, { client });
1021
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DoctorScreen, { client });
821
1022
  break;
822
1023
  case "config":
823
- body = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ConfigScreen, { client });
1024
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ConfigScreen, { client });
824
1025
  break;
825
1026
  case "routes":
826
- body = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(RoutesScreen, { client });
1027
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(RoutesScreen, { client });
827
1028
  break;
828
1029
  case "tokens":
829
- body = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TokensScreen, { client });
1030
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TokensScreen, { client });
830
1031
  break;
831
1032
  case "log":
832
- body = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AdminLogScreen, { client });
1033
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AdminLogScreen, { client });
833
1034
  break;
834
1035
  case "catalog":
835
- body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Pass a BillingAdminClient to see the catalog here." });
1036
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass a BillingAdminClient to see the catalog here." });
836
1037
  break;
837
1038
  case "subscriber":
838
- body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SubscriberScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Pass a BillingAdminClient to look up subscribers here." });
1039
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SubscriberScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass a BillingAdminClient to look up subscribers here." });
1040
+ break;
1041
+ case "audit":
1042
+ body = auditClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AuditScreen, { client: auditClient }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass an AuditAdminClient to see the audit trail here." });
839
1043
  break;
840
1044
  case "users":
841
- body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(UsersScreen, { client: authClient }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Pass an AuthAdminClient to look up users here." });
1045
+ body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(UsersScreen, { client: authClient }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass an AuthAdminClient to look up users here." });
842
1046
  break;
843
1047
  case "settings":
844
- body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
845
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
846
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1048
+ body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1049
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
847
1051
  import_react_config_admin_screens.ConfigEditorScreen,
848
1052
  {
849
1053
  client: configClient,
@@ -853,7 +1057,7 @@ function AdminShell({
853
1057
  onSaved: () => setEditing(null)
854
1058
  }
855
1059
  )
856
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1060
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
857
1061
  import_react_config_admin_screens.ConfigListScreen,
858
1062
  {
859
1063
  client: configClient,
@@ -864,9 +1068,9 @@ function AdminShell({
864
1068
  );
865
1069
  break;
866
1070
  case "templates":
867
- body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
868
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
869
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1071
+ body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1072
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
1073
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
870
1074
  import_react_courier_admin_screens.TemplateEditorScreen,
871
1075
  {
872
1076
  client: courierClient,
@@ -874,7 +1078,7 @@ function AdminShell({
874
1078
  onSaved: () => setEditing(null)
875
1079
  }
876
1080
  )
877
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1081
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
878
1082
  import_react_courier_admin_screens.TemplateListScreen,
879
1083
  {
880
1084
  client: courierClient,
@@ -883,13 +1087,13 @@ function AdminShell({
883
1087
  );
884
1088
  break;
885
1089
  }
886
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.shell, children: [
887
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("nav", { style: styles.nav, "aria-label": "Admin", children: NAV.map((g) => {
1090
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.shell, children: [
1091
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("nav", { style: styles.nav, "aria-label": "Admin", children: NAV.map((g) => {
888
1092
  const items = g.items.filter((i) => !i.needs || has[i.needs]);
889
1093
  if (items.length === 0) return null;
890
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
891
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.navGroup, children: g.group }),
892
- items.map((i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1094
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
1095
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: styles.navGroup, children: g.group }),
1096
+ items.map((i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
893
1097
  "button",
894
1098
  {
895
1099
  type: "button",
@@ -905,7 +1109,7 @@ function AdminShell({
905
1109
  ))
906
1110
  ] }, g.group);
907
1111
  }) }),
908
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("main", { style: styles.main, children: body })
1112
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("main", { style: styles.main, children: body })
909
1113
  ] });
910
1114
  }
911
1115
  // Annotate the CommonJS export names for ESM import in node:
@@ -913,6 +1117,7 @@ function AdminShell({
913
1117
  AdminLogScreen,
914
1118
  AdminShell,
915
1119
  AttentionScreen,
1120
+ AuditScreen,
916
1121
  CatalogScreen,
917
1122
  ConfigScreen,
918
1123
  DoctorScreen,