@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/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/vue-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
  ```ts
33
- h(AdminShell, { client, configClient, courierClient, authClient, billingClient })
34
+ h(AdminShell, { client, configClient, courierClient, authClient, billingClient, auditClient })
34
35
  ```
35
36
 
36
37
  Pass `page` and listen to `navigate` to own the URL (one route per page);
37
38
  omit `page` and the shell keeps it itself. Settings and Messaging appear only
38
39
  when 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.ts
38
39
  var import_vue_config_admin_screens = require("@fonderie/vue-config-admin-screens");
39
40
  var import_vue_courier_admin_screens = require("@fonderie/vue-courier-admin-screens");
40
- var import_vue12 = require("vue");
41
+ var import_vue13 = require("vue");
41
42
 
42
43
  // src/styles.ts
43
44
  var styles = {
@@ -418,17 +419,28 @@ var RoutesScreen = (0, import_vue7.defineComponent)({
418
419
  // src/screens/TokensScreen.ts
419
420
  var import_vue_admin7 = require("@fonderie/vue-admin");
420
421
  var import_vue8 = require("vue");
422
+ var ALL = ["read", "write", "secrets"];
421
423
  var TokensScreen = (0, import_vue8.defineComponent)({
422
424
  name: "FonderieTokensScreen",
423
425
  props: { client: { type: Object, required: true } },
424
426
  setup(props) {
425
- const { report, isLoading, error } = (0, import_vue_admin7.useAdminTokens)(props.client);
426
- return () => page("Access", { isLoading, error }, () => {
427
+ const { report, isLoading, error, issue, revoke } = (0, import_vue_admin7.useAdminTokens)(props.client);
428
+ const name = (0, import_vue8.ref)("");
429
+ const scopes = (0, import_vue8.ref)(["read"]);
430
+ const days = (0, import_vue8.ref)("");
431
+ const minted = (0, import_vue8.ref)(null);
432
+ const toggle = (s) => scopes.value = scopes.value.includes(s) ? scopes.value.filter((x) => x !== s) : [...scopes.value, s];
433
+ return () => {
427
434
  const r = report.value;
428
- if (!r) return null;
429
- return [
430
- (0, import_vue8.h)("h2", { style: styles.subtitle }, "Admin token"),
431
- (0, import_vue8.h)("p", [
435
+ return (0, import_vue8.h)("div", { style: styles.container }, [
436
+ (0, import_vue8.h)("h1", { style: styles.title }, "Access"),
437
+ error.value ? (0, import_vue8.h)(
438
+ "p",
439
+ { style: styles.error, role: "alert" },
440
+ error.value.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.value.explanation
441
+ ) : null,
442
+ (0, import_vue8.h)("h2", { style: styles.subtitle }, "Root token"),
443
+ r ? (0, import_vue8.h)("p", [
432
444
  (0, import_vue8.h)(
433
445
  "span",
434
446
  { style: r.admin.ok ? styles.ok : styles.bad },
@@ -437,16 +449,124 @@ var TokensScreen = (0, import_vue8.defineComponent)({
437
449
  ...r.admin.problems.map(
438
450
  (p) => (0, import_vue8.h)("div", { key: p.message, style: styles.bad }, p.message)
439
451
  )
440
- ]),
452
+ ]) : null,
453
+ (0, import_vue8.h)("h2", { style: styles.subtitle }, "Issued tokens"),
454
+ r?.issued === null ? (0, import_vue8.h)(
455
+ "p",
456
+ { style: styles.muted },
457
+ "Issuing is off \u2014 give AdminModule a store and run its migrations."
458
+ ) : [
459
+ minted.value ? (0, import_vue8.h)("p", { style: styles.ok }, [
460
+ "Copy this now \u2014 it is never shown again. ",
461
+ (0, import_vue8.h)("span", { style: styles.mono }, minted.value.token),
462
+ ` (${minted.value.name})`
463
+ ]) : null,
464
+ (0, import_vue8.h)(
465
+ "form",
466
+ {
467
+ style: styles.toolbar,
468
+ onSubmit: (e) => {
469
+ e.preventDefault();
470
+ const d = Number(days.value);
471
+ void issue({
472
+ name: name.value.trim(),
473
+ scopes: scopes.value,
474
+ ...days.value && Number.isInteger(d) && d > 0 ? { expiresInDays: d } : {}
475
+ }).then((t) => {
476
+ minted.value = { name: t.name, token: t.token };
477
+ name.value = "";
478
+ days.value = "";
479
+ }).catch(() => {
480
+ });
481
+ }
482
+ },
483
+ [
484
+ (0, import_vue8.h)("input", {
485
+ value: name.value,
486
+ onInput: (e) => name.value = e.target.value,
487
+ placeholder: "name",
488
+ style: { ...styles.button, cursor: "text" },
489
+ "aria-label": "Name"
490
+ }),
491
+ ...ALL.map(
492
+ (s) => (0, import_vue8.h)("label", { key: s, style: { ...styles.badge, cursor: "pointer" } }, [
493
+ (0, import_vue8.h)("input", {
494
+ type: "checkbox",
495
+ checked: scopes.value.includes(s),
496
+ onChange: () => toggle(s)
497
+ }),
498
+ ` ${s}`
499
+ ])
500
+ ),
501
+ (0, import_vue8.h)("input", {
502
+ value: days.value,
503
+ onInput: (e) => days.value = e.target.value,
504
+ placeholder: "days (optional)",
505
+ style: { ...styles.button, cursor: "text", width: "140px" },
506
+ "aria-label": "Days"
507
+ }),
508
+ (0, import_vue8.h)(
509
+ "button",
510
+ {
511
+ type: "submit",
512
+ style: styles.button,
513
+ disabled: !name.value.trim() || scopes.value.length === 0 || isLoading.value
514
+ },
515
+ "Issue"
516
+ )
517
+ ]
518
+ ),
519
+ r?.issued && r.issued.length > 0 ? table(
520
+ ["Name", "Scopes", "Created", "Expires", "Last used", ""],
521
+ r.issued.map(
522
+ (t) => (0, import_vue8.h)("tr", { key: t.id }, [
523
+ td([
524
+ t.name,
525
+ t.revokedAt ? (0, import_vue8.h)("span", { style: styles.badge }, " revoked") : ""
526
+ ]),
527
+ td(t.scopes.join(", "), styles.mono),
528
+ td(
529
+ `${new Date(t.createdAt).toLocaleDateString()} \xB7 ${t.createdBy}`,
530
+ styles.muted
531
+ ),
532
+ td(
533
+ t.expiresAt ? new Date(t.expiresAt).toLocaleDateString() : "never",
534
+ styles.muted
535
+ ),
536
+ td(
537
+ t.lastUsedAt ? new Date(t.lastUsedAt).toLocaleString() : "never",
538
+ styles.muted
539
+ ),
540
+ td(
541
+ t.revokedAt ? "" : (0, import_vue8.h)(
542
+ "button",
543
+ {
544
+ type: "button",
545
+ style: styles.button,
546
+ onClick: () => {
547
+ if (window.confirm(
548
+ `Revoke "${t.name}"? Anything using it stops working immediately.`
549
+ ))
550
+ void revoke(t.id).catch(() => {
551
+ });
552
+ }
553
+ },
554
+ "Revoke"
555
+ )
556
+ )
557
+ ])
558
+ )
559
+ ) : (0, import_vue8.h)("p", { style: styles.muted }, "None issued.")
560
+ ],
441
561
  (0, import_vue8.h)("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
442
- r.legacy.length === 0 ? (0, import_vue8.h)(
562
+ r && r.legacy.length === 0 ? (0, import_vue8.h)(
443
563
  "p",
444
564
  { style: styles.muted },
445
565
  "None \u2014 every brick's admin surface goes through this token."
446
566
  ) : (0, import_vue8.h)(
447
567
  "ul",
448
568
  { style: styles.list },
449
- r.legacy.map(
569
+ (r?.legacy ?? []).map(
450
570
  (l) => (0, import_vue8.h)("li", { key: l.module, style: styles.row }, [
451
571
  (0, import_vue8.h)("span", { style: styles.mono }, l.module),
452
572
  " ",
@@ -458,8 +578,8 @@ var TokensScreen = (0, import_vue8.defineComponent)({
458
578
  ])
459
579
  )
460
580
  )
461
- ];
462
- });
581
+ ]);
582
+ };
463
583
  }
464
584
  });
465
585
 
@@ -824,6 +944,85 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
824
944
  }
825
945
  });
826
946
 
947
+ // src/screens/AuditScreen.ts
948
+ var import_vue_admin11 = require("@fonderie/vue-admin");
949
+ var import_vue12 = require("vue");
950
+ var AuditScreen = (0, import_vue12.defineComponent)({
951
+ name: "FonderieAuditScreen",
952
+ props: {
953
+ client: { type: Object, required: true },
954
+ pageSize: { type: Number, default: 50 }
955
+ },
956
+ setup(props) {
957
+ const draft = (0, import_vue12.ref)({ workspaceId: "", type: "", actorId: "" });
958
+ const query = (0, import_vue12.ref)({ limit: props.pageSize });
959
+ const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_vue_admin11.useAdminAudit)(
960
+ props.client,
961
+ query
962
+ );
963
+ const field = (key, placeholder) => (0, import_vue12.h)("input", {
964
+ value: draft.value[key],
965
+ onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
966
+ placeholder,
967
+ style: { ...styles.button, cursor: "text" },
968
+ "aria-label": placeholder
969
+ });
970
+ return () => (0, import_vue12.h)("div", { style: styles.container }, [
971
+ (0, import_vue12.h)("h1", { style: styles.title }, "Audit"),
972
+ (0, import_vue12.h)(
973
+ "form",
974
+ {
975
+ style: styles.toolbar,
976
+ onSubmit: (e) => {
977
+ e.preventDefault();
978
+ const d = draft.value;
979
+ query.value = {
980
+ limit: props.pageSize,
981
+ ...d.workspaceId.trim() ? { workspaceId: d.workspaceId.trim() } : {},
982
+ ...d.type.trim() ? { type: d.type.trim() } : {},
983
+ ...d.actorId.trim() ? { actorId: d.actorId.trim() } : {}
984
+ };
985
+ }
986
+ },
987
+ [
988
+ field("workspaceId", "workspace id (all if empty)"),
989
+ field("type", "event type"),
990
+ field("actorId", "actor id"),
991
+ (0, import_vue12.h)(
992
+ "button",
993
+ { type: "submit", style: styles.button, disabled: isLoading.value },
994
+ "Filter"
995
+ ),
996
+ refreshButton("Refresh", isLoading.value, () => void refresh())
997
+ ]
998
+ ),
999
+ error.value ? (0, import_vue12.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
1000
+ events.value.length === 0 && !isLoading.value ? (0, import_vue12.h)("p", { style: styles.muted }, "Nothing recorded for this filter.") : table(
1001
+ ["When", "Type", "Workspace", "Actor", "Request"],
1002
+ events.value.map(
1003
+ (e) => (0, import_vue12.h)("tr", { key: e.id }, [
1004
+ td(new Date(e.createdAt).toLocaleString(), styles.muted),
1005
+ td(e.type, styles.mono),
1006
+ td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
1007
+ td(e.actorId ?? "\u2014", styles.mono),
1008
+ td(e.requestId ?? "", styles.muted)
1009
+ ])
1010
+ )
1011
+ ),
1012
+ isLoading.value ? (0, import_vue12.h)("p", { style: styles.status }, "Loading\u2026") : null,
1013
+ hasMore.value && !isLoading.value ? (0, import_vue12.h)(
1014
+ "button",
1015
+ {
1016
+ type: "button",
1017
+ style: { ...styles.button, marginTop: "8px" },
1018
+ onClick: () => void loadMore()
1019
+ },
1020
+ "Load more"
1021
+ ) : null
1022
+ ]);
1023
+ }
1024
+ });
1025
+
827
1026
  // src/screens/AdminShell.ts
828
1027
  var NAV = [
829
1028
  { group: "Today", items: [{ page: "attention", label: "Attention" }] },
@@ -849,12 +1048,13 @@ var NAV = [
849
1048
  {
850
1049
  group: "Activity",
851
1050
  items: [
1051
+ { page: "audit", label: "Audit", needs: "audit" },
852
1052
  { page: "log", label: "Admin log" },
853
1053
  { page: "tokens", label: "Access" }
854
1054
  ]
855
1055
  }
856
1056
  ];
857
- var AdminShell = (0, import_vue12.defineComponent)({
1057
+ var AdminShell = (0, import_vue13.defineComponent)({
858
1058
  name: "FonderieAdminShell",
859
1059
  props: {
860
1060
  client: { type: Object, required: true },
@@ -866,6 +1066,8 @@ var AdminShell = (0, import_vue12.defineComponent)({
866
1066
  authClient: { type: Object, default: void 0 },
867
1067
  // Given ⇒ the Money pages (catalog, subscriber) appear; needs @fonderie/billing ≥ 9.10.
868
1068
  billingClient: { type: Object, default: void 0 },
1069
+ // Given ⇒ the Audit page appears; needs @fonderie/audit ≥ 5.2.
1070
+ auditClient: { type: Object, default: void 0 },
869
1071
  environment: { type: String, default: void 0 },
870
1072
  // Controlled navigation: pass `page` and listen to `navigate` to own the
871
1073
  // URL. Omit `page` and the shell keeps it itself.
@@ -873,15 +1075,15 @@ var AdminShell = (0, import_vue12.defineComponent)({
873
1075
  },
874
1076
  emits: { navigate: (_page) => true },
875
1077
  setup(props, { emit }) {
876
- const own = (0, import_vue12.ref)("attention");
877
- const current = (0, import_vue12.computed)(() => props.page ?? own.value);
878
- const editing = (0, import_vue12.ref)(null);
1078
+ const own = (0, import_vue13.ref)("attention");
1079
+ const current = (0, import_vue13.computed)(() => props.page ?? own.value);
1080
+ const editing = (0, import_vue13.ref)(null);
879
1081
  const go = (p) => {
880
1082
  editing.value = null;
881
1083
  emit("navigate", p);
882
1084
  if (props.page === void 0) own.value = p;
883
1085
  };
884
- const back = () => (0, import_vue12.h)(
1086
+ const back = () => (0, import_vue13.h)(
885
1087
  "button",
886
1088
  { type: "button", style: styles.button, onClick: () => editing.value = null },
887
1089
  "\u2190 Back"
@@ -890,37 +1092,43 @@ var AdminShell = (0, import_vue12.defineComponent)({
890
1092
  const c = props.client;
891
1093
  switch (current.value) {
892
1094
  case "attention":
893
- return (0, import_vue12.h)(AttentionScreen, { client: c });
1095
+ return (0, import_vue13.h)(AttentionScreen, { client: c });
894
1096
  case "modules":
895
- return (0, import_vue12.h)(ModulesScreen, { client: c });
1097
+ return (0, import_vue13.h)(ModulesScreen, { client: c });
896
1098
  case "doctor":
897
- return (0, import_vue12.h)(DoctorScreen, { client: c });
1099
+ return (0, import_vue13.h)(DoctorScreen, { client: c });
898
1100
  case "config":
899
- return (0, import_vue12.h)(ConfigScreen, { client: c });
1101
+ return (0, import_vue13.h)(ConfigScreen, { client: c });
900
1102
  case "routes":
901
- return (0, import_vue12.h)(RoutesScreen, { client: c });
1103
+ return (0, import_vue13.h)(RoutesScreen, { client: c });
902
1104
  case "tokens":
903
- return (0, import_vue12.h)(TokensScreen, { client: c });
1105
+ return (0, import_vue13.h)(TokensScreen, { client: c });
904
1106
  case "log":
905
- return (0, import_vue12.h)(AdminLogScreen, { client: c });
1107
+ return (0, import_vue13.h)(AdminLogScreen, { client: c });
906
1108
  case "catalog":
907
- return props.billingClient ? (0, import_vue12.h)(CatalogScreen, { client: props.billingClient }) : (0, import_vue12.h)(
1109
+ return props.billingClient ? (0, import_vue13.h)(CatalogScreen, { client: props.billingClient }) : (0, import_vue13.h)(
908
1110
  "p",
909
1111
  { style: styles.status },
910
1112
  "Pass a BillingAdminClient to see the catalog here."
911
1113
  );
912
1114
  case "subscriber":
913
- return props.billingClient ? (0, import_vue12.h)(SubscriberScreen, { client: props.billingClient }) : (0, import_vue12.h)(
1115
+ return props.billingClient ? (0, import_vue13.h)(SubscriberScreen, { client: props.billingClient }) : (0, import_vue13.h)(
914
1116
  "p",
915
1117
  { style: styles.status },
916
1118
  "Pass a BillingAdminClient to look up subscribers here."
917
1119
  );
1120
+ case "audit":
1121
+ return props.auditClient ? (0, import_vue13.h)(AuditScreen, { client: props.auditClient }) : (0, import_vue13.h)(
1122
+ "p",
1123
+ { style: styles.status },
1124
+ "Pass an AuditAdminClient to see the audit trail here."
1125
+ );
918
1126
  case "users":
919
- return props.authClient ? (0, import_vue12.h)(UsersScreen, { client: props.authClient }) : (0, import_vue12.h)("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
1127
+ return props.authClient ? (0, import_vue13.h)(UsersScreen, { client: props.authClient }) : (0, import_vue13.h)("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
920
1128
  case "settings": {
921
1129
  const cc = props.configClient;
922
1130
  if (!cc)
923
- return (0, import_vue12.h)(
1131
+ return (0, import_vue13.h)(
924
1132
  "p",
925
1133
  { style: styles.status },
926
1134
  "Pass a ConfigAdminClient to manage config and secrets here."
@@ -929,7 +1137,7 @@ var AdminShell = (0, import_vue12.defineComponent)({
929
1137
  if (e && e.kind !== "template")
930
1138
  return [
931
1139
  back(),
932
- (0, import_vue12.h)(import_vue_config_admin_screens.ConfigEditorScreen, {
1140
+ (0, import_vue13.h)(import_vue_config_admin_screens.ConfigEditorScreen, {
933
1141
  client: cc,
934
1142
  kind: e.kind,
935
1143
  configKey: e.key,
@@ -937,7 +1145,7 @@ var AdminShell = (0, import_vue12.defineComponent)({
937
1145
  onSaved: () => editing.value = null
938
1146
  })
939
1147
  ];
940
- return (0, import_vue12.h)(import_vue_config_admin_screens.ConfigListScreen, {
1148
+ return (0, import_vue13.h)(import_vue_config_admin_screens.ConfigListScreen, {
941
1149
  client: cc,
942
1150
  ...props.environment !== void 0 ? { environment: props.environment } : {},
943
1151
  "onSelect-config": (key) => editing.value = { kind: "config", key },
@@ -947,7 +1155,7 @@ var AdminShell = (0, import_vue12.defineComponent)({
947
1155
  case "templates": {
948
1156
  const kc = props.courierClient;
949
1157
  if (!kc)
950
- return (0, import_vue12.h)(
1158
+ return (0, import_vue13.h)(
951
1159
  "p",
952
1160
  { style: styles.status },
953
1161
  "Pass a CourierAdminClient to manage templates here."
@@ -956,32 +1164,32 @@ var AdminShell = (0, import_vue12.defineComponent)({
956
1164
  if (e?.kind === "template")
957
1165
  return [
958
1166
  back(),
959
- (0, import_vue12.h)(import_vue_courier_admin_screens.TemplateEditorScreen, {
1167
+ (0, import_vue13.h)(import_vue_courier_admin_screens.TemplateEditorScreen, {
960
1168
  client: kc,
961
1169
  type: e.type,
962
1170
  onSaved: () => editing.value = null
963
1171
  })
964
1172
  ];
965
- return (0, import_vue12.h)(import_vue_courier_admin_screens.TemplateListScreen, {
1173
+ return (0, import_vue13.h)(import_vue_courier_admin_screens.TemplateListScreen, {
966
1174
  client: kc,
967
1175
  "onSelect-template": (t) => editing.value = { kind: "template", type: t.type }
968
1176
  });
969
1177
  }
970
1178
  }
971
1179
  };
972
- return () => (0, import_vue12.h)("div", { style: styles.shell }, [
973
- (0, import_vue12.h)(
1180
+ return () => (0, import_vue13.h)("div", { style: styles.shell }, [
1181
+ (0, import_vue13.h)(
974
1182
  "nav",
975
1183
  { style: styles.nav, "aria-label": "Admin" },
976
1184
  NAV.map((g) => {
977
1185
  const items = g.items.filter(
978
- (i) => !i.needs || (i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : props.billingClient)
1186
+ (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)
979
1187
  );
980
1188
  if (items.length === 0) return null;
981
- return (0, import_vue12.h)("div", { key: g.group }, [
982
- (0, import_vue12.h)("div", { style: styles.navGroup }, g.group),
1189
+ return (0, import_vue13.h)("div", { key: g.group }, [
1190
+ (0, import_vue13.h)("div", { style: styles.navGroup }, g.group),
983
1191
  ...items.map(
984
- (i) => (0, import_vue12.h)(
1192
+ (i) => (0, import_vue13.h)(
985
1193
  "button",
986
1194
  {
987
1195
  key: i.page,
@@ -999,7 +1207,7 @@ var AdminShell = (0, import_vue12.defineComponent)({
999
1207
  ]);
1000
1208
  })
1001
1209
  ),
1002
- (0, import_vue12.h)("main", { style: styles.main }, body())
1210
+ (0, import_vue13.h)("main", { style: styles.main }, body())
1003
1211
  ]);
1004
1212
  }
1005
1213
  });
@@ -1008,6 +1216,7 @@ var AdminShell = (0, import_vue12.defineComponent)({
1008
1216
  AdminLogScreen,
1009
1217
  AdminShell,
1010
1218
  AttentionScreen,
1219
+ AuditScreen,
1011
1220
  CatalogScreen,
1012
1221
  ConfigScreen,
1013
1222
  DoctorScreen,