@fonderie/vue-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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/screens/AdminShell.ts
2
2
  import { ConfigEditorScreen, ConfigListScreen } from "@fonderie/vue-config-admin-screens";
3
3
  import { TemplateEditorScreen, TemplateListScreen } from "@fonderie/vue-courier-admin-screens";
4
- import { computed as computed2, defineComponent as defineComponent11, h as h12, ref as ref3 } from "vue";
4
+ import { computed as computed2, defineComponent as defineComponent12, h as h13, ref as ref5 } from "vue";
5
5
 
6
6
  // src/styles.ts
7
7
  var styles = {
@@ -381,18 +381,29 @@ var RoutesScreen = defineComponent6({
381
381
 
382
382
  // src/screens/TokensScreen.ts
383
383
  import { useAdminTokens } from "@fonderie/vue-admin";
384
- import { defineComponent as defineComponent7, h as h8 } from "vue";
384
+ import { defineComponent as defineComponent7, h as h8, ref } from "vue";
385
+ var ALL = ["read", "write", "secrets"];
385
386
  var TokensScreen = defineComponent7({
386
387
  name: "FonderieTokensScreen",
387
388
  props: { client: { type: Object, required: true } },
388
389
  setup(props) {
389
- const { report, isLoading, error } = useAdminTokens(props.client);
390
- return () => page("Access", { isLoading, error }, () => {
390
+ const { report, isLoading, error, issue, revoke } = useAdminTokens(props.client);
391
+ const name = ref("");
392
+ const scopes = ref(["read"]);
393
+ const days = ref("");
394
+ const minted = ref(null);
395
+ const toggle = (s) => scopes.value = scopes.value.includes(s) ? scopes.value.filter((x) => x !== s) : [...scopes.value, s];
396
+ return () => {
391
397
  const r = report.value;
392
- if (!r) return null;
393
- return [
394
- h8("h2", { style: styles.subtitle }, "Admin token"),
395
- h8("p", [
398
+ return h8("div", { style: styles.container }, [
399
+ h8("h1", { style: styles.title }, "Access"),
400
+ error.value ? h8(
401
+ "p",
402
+ { style: styles.error, role: "alert" },
403
+ error.value.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.value.explanation
404
+ ) : null,
405
+ h8("h2", { style: styles.subtitle }, "Root token"),
406
+ r ? h8("p", [
396
407
  h8(
397
408
  "span",
398
409
  { style: r.admin.ok ? styles.ok : styles.bad },
@@ -401,16 +412,124 @@ var TokensScreen = defineComponent7({
401
412
  ...r.admin.problems.map(
402
413
  (p) => h8("div", { key: p.message, style: styles.bad }, p.message)
403
414
  )
404
- ]),
415
+ ]) : null,
416
+ h8("h2", { style: styles.subtitle }, "Issued tokens"),
417
+ r?.issued === null ? h8(
418
+ "p",
419
+ { style: styles.muted },
420
+ "Issuing is off \u2014 give AdminModule a store and run its migrations."
421
+ ) : [
422
+ minted.value ? h8("p", { style: styles.ok }, [
423
+ "Copy this now \u2014 it is never shown again. ",
424
+ h8("span", { style: styles.mono }, minted.value.token),
425
+ ` (${minted.value.name})`
426
+ ]) : null,
427
+ h8(
428
+ "form",
429
+ {
430
+ style: styles.toolbar,
431
+ onSubmit: (e) => {
432
+ e.preventDefault();
433
+ const d = Number(days.value);
434
+ void issue({
435
+ name: name.value.trim(),
436
+ scopes: scopes.value,
437
+ ...days.value && Number.isInteger(d) && d > 0 ? { expiresInDays: d } : {}
438
+ }).then((t) => {
439
+ minted.value = { name: t.name, token: t.token };
440
+ name.value = "";
441
+ days.value = "";
442
+ }).catch(() => {
443
+ });
444
+ }
445
+ },
446
+ [
447
+ h8("input", {
448
+ value: name.value,
449
+ onInput: (e) => name.value = e.target.value,
450
+ placeholder: "name",
451
+ style: { ...styles.button, cursor: "text" },
452
+ "aria-label": "Name"
453
+ }),
454
+ ...ALL.map(
455
+ (s) => h8("label", { key: s, style: { ...styles.badge, cursor: "pointer" } }, [
456
+ h8("input", {
457
+ type: "checkbox",
458
+ checked: scopes.value.includes(s),
459
+ onChange: () => toggle(s)
460
+ }),
461
+ ` ${s}`
462
+ ])
463
+ ),
464
+ h8("input", {
465
+ value: days.value,
466
+ onInput: (e) => days.value = e.target.value,
467
+ placeholder: "days (optional)",
468
+ style: { ...styles.button, cursor: "text", width: "140px" },
469
+ "aria-label": "Days"
470
+ }),
471
+ h8(
472
+ "button",
473
+ {
474
+ type: "submit",
475
+ style: styles.button,
476
+ disabled: !name.value.trim() || scopes.value.length === 0 || isLoading.value
477
+ },
478
+ "Issue"
479
+ )
480
+ ]
481
+ ),
482
+ r?.issued && r.issued.length > 0 ? table(
483
+ ["Name", "Scopes", "Created", "Expires", "Last used", ""],
484
+ r.issued.map(
485
+ (t) => h8("tr", { key: t.id }, [
486
+ td([
487
+ t.name,
488
+ t.revokedAt ? h8("span", { style: styles.badge }, " revoked") : ""
489
+ ]),
490
+ td(t.scopes.join(", "), styles.mono),
491
+ td(
492
+ `${new Date(t.createdAt).toLocaleDateString()} \xB7 ${t.createdBy}`,
493
+ styles.muted
494
+ ),
495
+ td(
496
+ t.expiresAt ? new Date(t.expiresAt).toLocaleDateString() : "never",
497
+ styles.muted
498
+ ),
499
+ td(
500
+ t.lastUsedAt ? new Date(t.lastUsedAt).toLocaleString() : "never",
501
+ styles.muted
502
+ ),
503
+ td(
504
+ t.revokedAt ? "" : h8(
505
+ "button",
506
+ {
507
+ type: "button",
508
+ style: styles.button,
509
+ onClick: () => {
510
+ if (window.confirm(
511
+ `Revoke "${t.name}"? Anything using it stops working immediately.`
512
+ ))
513
+ void revoke(t.id).catch(() => {
514
+ });
515
+ }
516
+ },
517
+ "Revoke"
518
+ )
519
+ )
520
+ ])
521
+ )
522
+ ) : h8("p", { style: styles.muted }, "None issued.")
523
+ ],
405
524
  h8("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
406
- r.legacy.length === 0 ? h8(
525
+ r && r.legacy.length === 0 ? h8(
407
526
  "p",
408
527
  { style: styles.muted },
409
528
  "None \u2014 every brick's admin surface goes through this token."
410
529
  ) : h8(
411
530
  "ul",
412
531
  { style: styles.list },
413
- r.legacy.map(
532
+ (r?.legacy ?? []).map(
414
533
  (l) => h8("li", { key: l.module, style: styles.row }, [
415
534
  h8("span", { style: styles.mono }, l.module),
416
535
  " ",
@@ -422,20 +541,20 @@ var TokensScreen = defineComponent7({
422
541
  ])
423
542
  )
424
543
  )
425
- ];
426
- });
544
+ ]);
545
+ };
427
546
  }
428
547
  });
429
548
 
430
549
  // src/screens/UsersScreen.ts
431
550
  import { useAdminLoginHistory, useAdminUser, useAdminUserSessions } from "@fonderie/vue-admin";
432
- import { computed, defineComponent as defineComponent8, h as h9, ref } from "vue";
551
+ import { computed, defineComponent as defineComponent8, h as h9, ref as ref2 } from "vue";
433
552
  var UsersScreen = defineComponent8({
434
553
  name: "FonderieUsersScreen",
435
554
  props: { client: { type: Object, required: true } },
436
555
  setup(props) {
437
- const input = ref("");
438
- const email = ref("");
556
+ const input = ref2("");
557
+ const email = ref2("");
439
558
  const { user, isLoading, error, suspend, unsuspend, revokeSessions } = useAdminUser(
440
559
  props.client,
441
560
  { email }
@@ -633,18 +752,18 @@ var CatalogScreen = defineComponent9({
633
752
 
634
753
  // src/screens/SubscriberScreen.ts
635
754
  import { useAdminSubscriber } from "@fonderie/vue-admin";
636
- import { defineComponent as defineComponent10, h as h11, ref as ref2 } from "vue";
755
+ import { defineComponent as defineComponent10, h as h11, ref as ref3 } from "vue";
637
756
  var SubscriberScreen = defineComponent10({
638
757
  name: "FonderieSubscriberScreen",
639
758
  props: { client: { type: Object, required: true } },
640
759
  setup(props) {
641
- const type = ref2("user");
642
- const idInput = ref2("");
643
- const subscriber = ref2(null);
760
+ const type = ref3("user");
761
+ const idInput = ref3("");
762
+ const subscriber = ref3(null);
644
763
  const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = useAdminSubscriber(props.client, subscriber, { limit: 20 });
645
- const amount = ref2("");
646
- const note = ref2("");
647
- const granted = ref2(null);
764
+ const amount = ref3("");
765
+ const note = ref3("");
766
+ const granted = ref3(null);
648
767
  const input = (model, placeholder, extra = {}) => h11("input", {
649
768
  value: model.value,
650
769
  onInput: (e) => model.value = e.target.value,
@@ -788,6 +907,85 @@ var SubscriberScreen = defineComponent10({
788
907
  }
789
908
  });
790
909
 
910
+ // src/screens/AuditScreen.ts
911
+ import { useAdminAudit } from "@fonderie/vue-admin";
912
+ import { defineComponent as defineComponent11, h as h12, ref as ref4 } from "vue";
913
+ var AuditScreen = defineComponent11({
914
+ name: "FonderieAuditScreen",
915
+ props: {
916
+ client: { type: Object, required: true },
917
+ pageSize: { type: Number, default: 50 }
918
+ },
919
+ setup(props) {
920
+ const draft = ref4({ workspaceId: "", type: "", actorId: "" });
921
+ const query = ref4({ limit: props.pageSize });
922
+ const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(
923
+ props.client,
924
+ query
925
+ );
926
+ const field = (key, placeholder) => h12("input", {
927
+ value: draft.value[key],
928
+ onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
929
+ placeholder,
930
+ style: { ...styles.button, cursor: "text" },
931
+ "aria-label": placeholder
932
+ });
933
+ return () => h12("div", { style: styles.container }, [
934
+ h12("h1", { style: styles.title }, "Audit"),
935
+ h12(
936
+ "form",
937
+ {
938
+ style: styles.toolbar,
939
+ onSubmit: (e) => {
940
+ e.preventDefault();
941
+ const d = draft.value;
942
+ query.value = {
943
+ limit: props.pageSize,
944
+ ...d.workspaceId.trim() ? { workspaceId: d.workspaceId.trim() } : {},
945
+ ...d.type.trim() ? { type: d.type.trim() } : {},
946
+ ...d.actorId.trim() ? { actorId: d.actorId.trim() } : {}
947
+ };
948
+ }
949
+ },
950
+ [
951
+ field("workspaceId", "workspace id (all if empty)"),
952
+ field("type", "event type"),
953
+ field("actorId", "actor id"),
954
+ h12(
955
+ "button",
956
+ { type: "submit", style: styles.button, disabled: isLoading.value },
957
+ "Filter"
958
+ ),
959
+ refreshButton("Refresh", isLoading.value, () => void refresh())
960
+ ]
961
+ ),
962
+ error.value ? h12("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
963
+ events.value.length === 0 && !isLoading.value ? h12("p", { style: styles.muted }, "Nothing recorded for this filter.") : table(
964
+ ["When", "Type", "Workspace", "Actor", "Request"],
965
+ events.value.map(
966
+ (e) => h12("tr", { key: e.id }, [
967
+ td(new Date(e.createdAt).toLocaleString(), styles.muted),
968
+ td(e.type, styles.mono),
969
+ td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
970
+ td(e.actorId ?? "\u2014", styles.mono),
971
+ td(e.requestId ?? "", styles.muted)
972
+ ])
973
+ )
974
+ ),
975
+ isLoading.value ? h12("p", { style: styles.status }, "Loading\u2026") : null,
976
+ hasMore.value && !isLoading.value ? h12(
977
+ "button",
978
+ {
979
+ type: "button",
980
+ style: { ...styles.button, marginTop: "8px" },
981
+ onClick: () => void loadMore()
982
+ },
983
+ "Load more"
984
+ ) : null
985
+ ]);
986
+ }
987
+ });
988
+
791
989
  // src/screens/AdminShell.ts
792
990
  var NAV = [
793
991
  { group: "Today", items: [{ page: "attention", label: "Attention" }] },
@@ -813,12 +1011,13 @@ var NAV = [
813
1011
  {
814
1012
  group: "Activity",
815
1013
  items: [
1014
+ { page: "audit", label: "Audit", needs: "audit" },
816
1015
  { page: "log", label: "Admin log" },
817
1016
  { page: "tokens", label: "Access" }
818
1017
  ]
819
1018
  }
820
1019
  ];
821
- var AdminShell = defineComponent11({
1020
+ var AdminShell = defineComponent12({
822
1021
  name: "FonderieAdminShell",
823
1022
  props: {
824
1023
  client: { type: Object, required: true },
@@ -830,6 +1029,8 @@ var AdminShell = defineComponent11({
830
1029
  authClient: { type: Object, default: void 0 },
831
1030
  // Given ⇒ the Money pages (catalog, subscriber) appear; needs @fonderie/billing ≥ 9.10.
832
1031
  billingClient: { type: Object, default: void 0 },
1032
+ // Given ⇒ the Audit page appears; needs @fonderie/audit ≥ 5.2.
1033
+ auditClient: { type: Object, default: void 0 },
833
1034
  environment: { type: String, default: void 0 },
834
1035
  // Controlled navigation: pass `page` and listen to `navigate` to own the
835
1036
  // URL. Omit `page` and the shell keeps it itself.
@@ -837,15 +1038,15 @@ var AdminShell = defineComponent11({
837
1038
  },
838
1039
  emits: { navigate: (_page) => true },
839
1040
  setup(props, { emit }) {
840
- const own = ref3("attention");
1041
+ const own = ref5("attention");
841
1042
  const current = computed2(() => props.page ?? own.value);
842
- const editing = ref3(null);
1043
+ const editing = ref5(null);
843
1044
  const go = (p) => {
844
1045
  editing.value = null;
845
1046
  emit("navigate", p);
846
1047
  if (props.page === void 0) own.value = p;
847
1048
  };
848
- const back = () => h12(
1049
+ const back = () => h13(
849
1050
  "button",
850
1051
  { type: "button", style: styles.button, onClick: () => editing.value = null },
851
1052
  "\u2190 Back"
@@ -854,37 +1055,43 @@ var AdminShell = defineComponent11({
854
1055
  const c = props.client;
855
1056
  switch (current.value) {
856
1057
  case "attention":
857
- return h12(AttentionScreen, { client: c });
1058
+ return h13(AttentionScreen, { client: c });
858
1059
  case "modules":
859
- return h12(ModulesScreen, { client: c });
1060
+ return h13(ModulesScreen, { client: c });
860
1061
  case "doctor":
861
- return h12(DoctorScreen, { client: c });
1062
+ return h13(DoctorScreen, { client: c });
862
1063
  case "config":
863
- return h12(ConfigScreen, { client: c });
1064
+ return h13(ConfigScreen, { client: c });
864
1065
  case "routes":
865
- return h12(RoutesScreen, { client: c });
1066
+ return h13(RoutesScreen, { client: c });
866
1067
  case "tokens":
867
- return h12(TokensScreen, { client: c });
1068
+ return h13(TokensScreen, { client: c });
868
1069
  case "log":
869
- return h12(AdminLogScreen, { client: c });
1070
+ return h13(AdminLogScreen, { client: c });
870
1071
  case "catalog":
871
- return props.billingClient ? h12(CatalogScreen, { client: props.billingClient }) : h12(
1072
+ return props.billingClient ? h13(CatalogScreen, { client: props.billingClient }) : h13(
872
1073
  "p",
873
1074
  { style: styles.status },
874
1075
  "Pass a BillingAdminClient to see the catalog here."
875
1076
  );
876
1077
  case "subscriber":
877
- return props.billingClient ? h12(SubscriberScreen, { client: props.billingClient }) : h12(
1078
+ return props.billingClient ? h13(SubscriberScreen, { client: props.billingClient }) : h13(
878
1079
  "p",
879
1080
  { style: styles.status },
880
1081
  "Pass a BillingAdminClient to look up subscribers here."
881
1082
  );
1083
+ case "audit":
1084
+ return props.auditClient ? h13(AuditScreen, { client: props.auditClient }) : h13(
1085
+ "p",
1086
+ { style: styles.status },
1087
+ "Pass an AuditAdminClient to see the audit trail here."
1088
+ );
882
1089
  case "users":
883
- return props.authClient ? h12(UsersScreen, { client: props.authClient }) : h12("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
1090
+ return props.authClient ? h13(UsersScreen, { client: props.authClient }) : h13("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
884
1091
  case "settings": {
885
1092
  const cc = props.configClient;
886
1093
  if (!cc)
887
- return h12(
1094
+ return h13(
888
1095
  "p",
889
1096
  { style: styles.status },
890
1097
  "Pass a ConfigAdminClient to manage config and secrets here."
@@ -893,7 +1100,7 @@ var AdminShell = defineComponent11({
893
1100
  if (e && e.kind !== "template")
894
1101
  return [
895
1102
  back(),
896
- h12(ConfigEditorScreen, {
1103
+ h13(ConfigEditorScreen, {
897
1104
  client: cc,
898
1105
  kind: e.kind,
899
1106
  configKey: e.key,
@@ -901,7 +1108,7 @@ var AdminShell = defineComponent11({
901
1108
  onSaved: () => editing.value = null
902
1109
  })
903
1110
  ];
904
- return h12(ConfigListScreen, {
1111
+ return h13(ConfigListScreen, {
905
1112
  client: cc,
906
1113
  ...props.environment !== void 0 ? { environment: props.environment } : {},
907
1114
  "onSelect-config": (key) => editing.value = { kind: "config", key },
@@ -911,7 +1118,7 @@ var AdminShell = defineComponent11({
911
1118
  case "templates": {
912
1119
  const kc = props.courierClient;
913
1120
  if (!kc)
914
- return h12(
1121
+ return h13(
915
1122
  "p",
916
1123
  { style: styles.status },
917
1124
  "Pass a CourierAdminClient to manage templates here."
@@ -920,32 +1127,32 @@ var AdminShell = defineComponent11({
920
1127
  if (e?.kind === "template")
921
1128
  return [
922
1129
  back(),
923
- h12(TemplateEditorScreen, {
1130
+ h13(TemplateEditorScreen, {
924
1131
  client: kc,
925
1132
  type: e.type,
926
1133
  onSaved: () => editing.value = null
927
1134
  })
928
1135
  ];
929
- return h12(TemplateListScreen, {
1136
+ return h13(TemplateListScreen, {
930
1137
  client: kc,
931
1138
  "onSelect-template": (t) => editing.value = { kind: "template", type: t.type }
932
1139
  });
933
1140
  }
934
1141
  }
935
1142
  };
936
- return () => h12("div", { style: styles.shell }, [
937
- h12(
1143
+ return () => h13("div", { style: styles.shell }, [
1144
+ h13(
938
1145
  "nav",
939
1146
  { style: styles.nav, "aria-label": "Admin" },
940
1147
  NAV.map((g) => {
941
1148
  const items = g.items.filter(
942
- (i) => !i.needs || (i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : props.billingClient)
1149
+ (i) => !i.needs || (i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : i.needs === "billing" ? props.billingClient : props.auditClient)
943
1150
  );
944
1151
  if (items.length === 0) return null;
945
- return h12("div", { key: g.group }, [
946
- h12("div", { style: styles.navGroup }, g.group),
1152
+ return h13("div", { key: g.group }, [
1153
+ h13("div", { style: styles.navGroup }, g.group),
947
1154
  ...items.map(
948
- (i) => h12(
1155
+ (i) => h13(
949
1156
  "button",
950
1157
  {
951
1158
  key: i.page,
@@ -963,7 +1170,7 @@ var AdminShell = defineComponent11({
963
1170
  ]);
964
1171
  })
965
1172
  ),
966
- h12("main", { style: styles.main }, body())
1173
+ h13("main", { style: styles.main }, body())
967
1174
  ]);
968
1175
  }
969
1176
  });
@@ -971,6 +1178,7 @@ export {
971
1178
  AdminLogScreen,
972
1179
  AdminShell,
973
1180
  AttentionScreen,
1181
+ AuditScreen,
974
1182
  CatalogScreen,
975
1183
  ConfigScreen,
976
1184
  DoctorScreen,