@fonderie/react-admin-screens 0.4.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,7 +14,7 @@ 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 | Audit · Admin log · Access | what happened, every workspace (`auditClient`) · who did what through here · 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
20
  import { AdminClient, AuditAdminClient, AuthAdminClient, BillingAdminClient, ConfigAdminClient, CourierAdminClient } from '@fonderie/client';
@@ -38,6 +38,13 @@ Pass `page` and `onNavigate` to own the URL (one route per page); omit both
38
38
  and the shell keeps the page itself. Settings and Messaging appear only when
39
39
  their client is given.
40
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
+
41
48
  Nothing here stores the token: the app constructs the clients and decides
42
49
  where the token lives. Every request through the shell lands in the admin
43
50
  log, including the ones the token refuses.
package/dist/index.cjs CHANGED
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(index_exports);
38
38
  // src/screens/AdminShell.tsx
39
39
  var import_react_config_admin_screens = require("@fonderie/react-config-admin-screens");
40
40
  var import_react_courier_admin_screens = require("@fonderie/react-courier-admin-screens");
41
- var import_react4 = require("react");
41
+ var import_react5 = require("react");
42
42
 
43
43
  // src/styles.ts
44
44
  var styles = {
@@ -346,34 +346,146 @@ function RoutesScreen({ client }) {
346
346
 
347
347
  // src/screens/TokensScreen.tsx
348
348
  var import_react_admin7 = require("@fonderie/react-admin");
349
+ var import_react = require("react");
349
350
  var import_jsx_runtime7 = require("react/jsx-runtime");
351
+ var ALL = ["read", "write", "secrets"];
350
352
  function TokensScreen({ client }) {
351
- 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]);
352
359
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: styles.container, children: [
353
360
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { style: styles.title, children: "Access" }),
354
- 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: [
355
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Admin token" }),
356
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
357
- 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" }),
358
- report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: styles.bad, children: p.message }, p.message))
359
- ] }),
360
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
361
- 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: [
362
- /* @__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.",
363
371
  " ",
364
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
365
- ] }, l.module)) })
366
- ] }) : 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)) })
367
479
  ] });
368
480
  }
369
481
 
370
482
  // src/screens/UsersScreen.tsx
371
483
  var import_react_admin8 = require("@fonderie/react-admin");
372
- var import_react = require("react");
484
+ var import_react2 = require("react");
373
485
  var import_jsx_runtime8 = require("react/jsx-runtime");
374
486
  function UsersScreen({ client }) {
375
- const [input, setInput] = (0, import_react.useState)("");
376
- 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);
377
489
  const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_react_admin8.useAdminUser)(
378
490
  client,
379
491
  email ? { email } : {}
@@ -583,16 +695,16 @@ function CatalogScreen({ client }) {
583
695
 
584
696
  // src/screens/SubscriberScreen.tsx
585
697
  var import_react_admin10 = require("@fonderie/react-admin");
586
- var import_react2 = require("react");
698
+ var import_react3 = require("react");
587
699
  var import_jsx_runtime10 = require("react/jsx-runtime");
588
700
  function SubscriberScreen({ client }) {
589
- const [type, setType] = (0, import_react2.useState)("user");
590
- const [idInput, setIdInput] = (0, import_react2.useState)("");
591
- 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);
592
704
  const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin10.useAdminSubscriber)(client, subscriber, { limit: 20 });
593
- const [amount, setAmount] = (0, import_react2.useState)("");
594
- const [note, setNote] = (0, import_react2.useState)("");
595
- 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);
596
708
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.container, children: [
597
709
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { style: styles.title, children: "Subscriber" }),
598
710
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -757,11 +869,11 @@ function SubscriberScreen({ client }) {
757
869
 
758
870
  // src/screens/AuditScreen.tsx
759
871
  var import_react_admin11 = require("@fonderie/react-admin");
760
- var import_react3 = require("react");
872
+ var import_react4 = require("react");
761
873
  var import_jsx_runtime11 = require("react/jsx-runtime");
762
874
  function AuditScreen({ client, pageSize = 50 }) {
763
- const [draft, setDraft] = (0, import_react3.useState)({ workspaceId: "", type: "", actorId: "" });
764
- const [filter, setFilter] = (0, import_react3.useState)(
875
+ const [draft, setDraft] = (0, import_react4.useState)({ workspaceId: "", type: "", actorId: "" });
876
+ const [filter, setFilter] = (0, import_react4.useState)(
765
877
  {}
766
878
  );
767
879
  const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin11.useAdminAudit)(client, {
@@ -883,13 +995,13 @@ function AdminShell({
883
995
  page,
884
996
  onNavigate
885
997
  }) {
886
- const [own, setOwn] = (0, import_react4.useState)("attention");
998
+ const [own, setOwn] = (0, import_react5.useState)("attention");
887
999
  const current = page ?? own;
888
1000
  const go = (p) => {
889
1001
  if (onNavigate) onNavigate(p);
890
1002
  if (page === void 0) setOwn(p);
891
1003
  };
892
- const [editing, setEditing] = (0, import_react4.useState)(null);
1004
+ const [editing, setEditing] = (0, import_react5.useState)(null);
893
1005
  const has = {
894
1006
  config: Boolean(configClient),
895
1007
  courier: Boolean(courierClient),