@fonderie/vue-admin-screens 0.5.0 → 0.7.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 CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  CatalogScreen: () => CatalogScreen,
28
28
  ConfigScreen: () => ConfigScreen,
29
29
  DoctorScreen: () => DoctorScreen,
30
+ MigrationsScreen: () => MigrationsScreen,
30
31
  ModulesScreen: () => ModulesScreen,
31
32
  RoutesScreen: () => RoutesScreen,
32
33
  SubscriberScreen: () => SubscriberScreen,
@@ -38,7 +39,7 @@ module.exports = __toCommonJS(index_exports);
38
39
  // src/screens/AdminShell.ts
39
40
  var import_vue_config_admin_screens = require("@fonderie/vue-config-admin-screens");
40
41
  var import_vue_courier_admin_screens = require("@fonderie/vue-courier-admin-screens");
41
- var import_vue13 = require("vue");
42
+ var import_vue14 = require("vue");
42
43
 
43
44
  // src/styles.ts
44
45
  var styles = {
@@ -416,52 +417,133 @@ var RoutesScreen = (0, import_vue7.defineComponent)({
416
417
  }
417
418
  });
418
419
 
419
- // src/screens/TokensScreen.ts
420
+ // src/screens/MigrationsScreen.ts
420
421
  var import_vue_admin7 = require("@fonderie/vue-admin");
421
422
  var import_vue8 = require("vue");
423
+ function why(m, everApplied) {
424
+ if (m.pending.length === 0) return null;
425
+ if (m.blockedBy) return `Apply "${m.blockedBy}" first \u2014 it runs before this one and is behind.`;
426
+ if (everApplied && m.pending.some((p) => p.impact === "destructive"))
427
+ return "Contains a migration that deletes data. No down-migration brings it back \u2014 apply this one through CI or `npm run migrate`.";
428
+ return null;
429
+ }
430
+ var MigrationsScreen = (0, import_vue8.defineComponent)({
431
+ name: "FonderieMigrationsScreen",
432
+ props: { client: { type: Object, required: true } },
433
+ setup(props) {
434
+ const { report, isLoading, error, refresh, apply } = (0, import_vue_admin7.useAdminMigrations)(props.client);
435
+ const section = (m, everApplied) => {
436
+ const blocked = why(m, everApplied);
437
+ const files = m.pending.map((p) => p.file);
438
+ return (0, import_vue8.h)("section", { style: { marginBottom: "24px" } }, [
439
+ (0, import_vue8.h)("h2", { style: styles.subtitle }, `${m.name} \u2014 ${m.pending.length} pending`),
440
+ (0, import_vue8.h)(
441
+ "ul",
442
+ m.pending.map(
443
+ (p) => (0, import_vue8.h)("li", [
444
+ (0, import_vue8.h)("code", p.file),
445
+ " ",
446
+ (0, import_vue8.h)(
447
+ "span",
448
+ { style: p.impact === "destructive" ? styles.bad : styles.advice },
449
+ p.impact
450
+ ),
451
+ p.destructive.length > 0 ? (0, import_vue8.h)(
452
+ "ul",
453
+ p.destructive.map((s) => (0, import_vue8.h)("li", [(0, import_vue8.h)("code", s.slice(0, 120))]))
454
+ ) : null
455
+ ])
456
+ )
457
+ ),
458
+ blocked ? (0, import_vue8.h)("p", { style: styles.advice }, blocked) : (0, import_vue8.h)(
459
+ "button",
460
+ {
461
+ type: "button",
462
+ style: styles.button,
463
+ disabled: isLoading.value || !m.appliable,
464
+ onClick: () => {
465
+ if (window.confirm(
466
+ `Apply ${m.pending.length} migration(s) to "${m.name}"? This changes the database schema.`
467
+ ))
468
+ void apply(m.name, files).catch(() => {
469
+ });
470
+ }
471
+ },
472
+ `Apply ${m.pending.length} migration${m.pending.length === 1 ? "" : "s"}`
473
+ )
474
+ ]);
475
+ };
476
+ return () => page(
477
+ "Migrations",
478
+ { isLoading, error },
479
+ () => {
480
+ const r = report.value;
481
+ if (!r) return null;
482
+ const behind = r.modules.filter((m) => m.pending.length > 0);
483
+ if (behind.length === 0) return (0, import_vue8.h)("p", { style: styles.ok }, "Every module is up to date.");
484
+ return [
485
+ r.everApplied ? null : (0, import_vue8.h)(
486
+ "p",
487
+ { style: styles.advice },
488
+ "This database has never been migrated \u2014 treating it as a first install, so nothing is held back."
489
+ ),
490
+ ...behind.map((m) => section(m, r.everApplied))
491
+ ].filter(Boolean);
492
+ },
493
+ [refreshButton("Refresh", isLoading.value, () => void refresh())],
494
+ {
495
+ errorText: (e) => e.status === 403 ? "Applying migrations needs a token with the write scope." : e.explanation
496
+ }
497
+ );
498
+ }
499
+ });
500
+
501
+ // src/screens/TokensScreen.ts
502
+ var import_vue_admin8 = require("@fonderie/vue-admin");
503
+ var import_vue9 = require("vue");
422
504
  var ALL = ["read", "write", "secrets"];
423
- var TokensScreen = (0, import_vue8.defineComponent)({
505
+ var TokensScreen = (0, import_vue9.defineComponent)({
424
506
  name: "FonderieTokensScreen",
425
507
  props: { client: { type: Object, required: true } },
426
508
  setup(props) {
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);
509
+ const { report, isLoading, error, issue, revoke } = (0, import_vue_admin8.useAdminTokens)(props.client);
510
+ const name = (0, import_vue9.ref)("");
511
+ const scopes = (0, import_vue9.ref)(["read"]);
512
+ const days = (0, import_vue9.ref)("");
513
+ const minted = (0, import_vue9.ref)(null);
432
514
  const toggle = (s) => scopes.value = scopes.value.includes(s) ? scopes.value.filter((x) => x !== s) : [...scopes.value, s];
433
515
  return () => {
434
516
  const r = report.value;
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)(
517
+ return (0, import_vue9.h)("div", { style: styles.container }, [
518
+ (0, import_vue9.h)("h1", { style: styles.title }, "Access"),
519
+ error.value ? (0, import_vue9.h)(
438
520
  "p",
439
521
  { style: styles.error, role: "alert" },
440
522
  error.value.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.value.explanation
441
523
  ) : null,
442
- (0, import_vue8.h)("h2", { style: styles.subtitle }, "Root token"),
443
- r ? (0, import_vue8.h)("p", [
444
- (0, import_vue8.h)(
524
+ (0, import_vue9.h)("h2", { style: styles.subtitle }, "Root token"),
525
+ r ? (0, import_vue9.h)("p", [
526
+ (0, import_vue9.h)(
445
527
  "span",
446
528
  { style: r.admin.ok ? styles.ok : styles.bad },
447
529
  r.admin.ok ? "strong" : "weak"
448
530
  ),
449
531
  ...r.admin.problems.map(
450
- (p) => (0, import_vue8.h)("div", { key: p.message, style: styles.bad }, p.message)
532
+ (p) => (0, import_vue9.h)("div", { key: p.message, style: styles.bad }, p.message)
451
533
  )
452
534
  ]) : null,
453
- (0, import_vue8.h)("h2", { style: styles.subtitle }, "Issued tokens"),
454
- r?.issued === null ? (0, import_vue8.h)(
535
+ (0, import_vue9.h)("h2", { style: styles.subtitle }, "Issued tokens"),
536
+ r?.issued === null ? (0, import_vue9.h)(
455
537
  "p",
456
538
  { style: styles.muted },
457
539
  "Issuing is off \u2014 give AdminModule a store and run its migrations."
458
540
  ) : [
459
- minted.value ? (0, import_vue8.h)("p", { style: styles.ok }, [
541
+ minted.value ? (0, import_vue9.h)("p", { style: styles.ok }, [
460
542
  "Copy this now \u2014 it is never shown again. ",
461
- (0, import_vue8.h)("span", { style: styles.mono }, minted.value.token),
543
+ (0, import_vue9.h)("span", { style: styles.mono }, minted.value.token),
462
544
  ` (${minted.value.name})`
463
545
  ]) : null,
464
- (0, import_vue8.h)(
546
+ (0, import_vue9.h)(
465
547
  "form",
466
548
  {
467
549
  style: styles.toolbar,
@@ -481,7 +563,7 @@ var TokensScreen = (0, import_vue8.defineComponent)({
481
563
  }
482
564
  },
483
565
  [
484
- (0, import_vue8.h)("input", {
566
+ (0, import_vue9.h)("input", {
485
567
  value: name.value,
486
568
  onInput: (e) => name.value = e.target.value,
487
569
  placeholder: "name",
@@ -489,8 +571,8 @@ var TokensScreen = (0, import_vue8.defineComponent)({
489
571
  "aria-label": "Name"
490
572
  }),
491
573
  ...ALL.map(
492
- (s) => (0, import_vue8.h)("label", { key: s, style: { ...styles.badge, cursor: "pointer" } }, [
493
- (0, import_vue8.h)("input", {
574
+ (s) => (0, import_vue9.h)("label", { key: s, style: { ...styles.badge, cursor: "pointer" } }, [
575
+ (0, import_vue9.h)("input", {
494
576
  type: "checkbox",
495
577
  checked: scopes.value.includes(s),
496
578
  onChange: () => toggle(s)
@@ -498,14 +580,14 @@ var TokensScreen = (0, import_vue8.defineComponent)({
498
580
  ` ${s}`
499
581
  ])
500
582
  ),
501
- (0, import_vue8.h)("input", {
583
+ (0, import_vue9.h)("input", {
502
584
  value: days.value,
503
585
  onInput: (e) => days.value = e.target.value,
504
586
  placeholder: "days (optional)",
505
587
  style: { ...styles.button, cursor: "text", width: "140px" },
506
588
  "aria-label": "Days"
507
589
  }),
508
- (0, import_vue8.h)(
590
+ (0, import_vue9.h)(
509
591
  "button",
510
592
  {
511
593
  type: "submit",
@@ -519,10 +601,10 @@ var TokensScreen = (0, import_vue8.defineComponent)({
519
601
  r?.issued && r.issued.length > 0 ? table(
520
602
  ["Name", "Scopes", "Created", "Expires", "Last used", ""],
521
603
  r.issued.map(
522
- (t) => (0, import_vue8.h)("tr", { key: t.id }, [
604
+ (t) => (0, import_vue9.h)("tr", { key: t.id }, [
523
605
  td([
524
606
  t.name,
525
- t.revokedAt ? (0, import_vue8.h)("span", { style: styles.badge }, " revoked") : ""
607
+ t.revokedAt ? (0, import_vue9.h)("span", { style: styles.badge }, " revoked") : ""
526
608
  ]),
527
609
  td(t.scopes.join(", "), styles.mono),
528
610
  td(
@@ -538,7 +620,7 @@ var TokensScreen = (0, import_vue8.defineComponent)({
538
620
  styles.muted
539
621
  ),
540
622
  td(
541
- t.revokedAt ? "" : (0, import_vue8.h)(
623
+ t.revokedAt ? "" : (0, import_vue9.h)(
542
624
  "button",
543
625
  {
544
626
  type: "button",
@@ -556,21 +638,21 @@ var TokensScreen = (0, import_vue8.defineComponent)({
556
638
  )
557
639
  ])
558
640
  )
559
- ) : (0, import_vue8.h)("p", { style: styles.muted }, "None issued.")
641
+ ) : (0, import_vue9.h)("p", { style: styles.muted }, "None issued.")
560
642
  ],
561
- (0, import_vue8.h)("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
562
- r && r.legacy.length === 0 ? (0, import_vue8.h)(
643
+ (0, import_vue9.h)("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
644
+ r && r.legacy.length === 0 ? (0, import_vue9.h)(
563
645
  "p",
564
646
  { style: styles.muted },
565
647
  "None \u2014 every brick's admin surface goes through this token."
566
- ) : (0, import_vue8.h)(
648
+ ) : (0, import_vue9.h)(
567
649
  "ul",
568
650
  { style: styles.list },
569
651
  (r?.legacy ?? []).map(
570
- (l) => (0, import_vue8.h)("li", { key: l.module, style: styles.row }, [
571
- (0, import_vue8.h)("span", { style: styles.mono }, l.module),
652
+ (l) => (0, import_vue9.h)("li", { key: l.module, style: styles.row }, [
653
+ (0, import_vue9.h)("span", { style: styles.mono }, l.module),
572
654
  " ",
573
- (0, import_vue8.h)(
655
+ (0, import_vue9.h)(
574
656
  "span",
575
657
  { style: styles.muted },
576
658
  "still registers its standalone routes with its own token (deprecated)"
@@ -584,38 +666,50 @@ var TokensScreen = (0, import_vue8.defineComponent)({
584
666
  });
585
667
 
586
668
  // src/screens/UsersScreen.ts
587
- var import_vue_admin8 = require("@fonderie/vue-admin");
588
- var import_vue9 = require("vue");
589
- var UsersScreen = (0, import_vue9.defineComponent)({
669
+ var import_vue_admin9 = require("@fonderie/vue-admin");
670
+ var import_vue10 = require("vue");
671
+ var UsersScreen = (0, import_vue10.defineComponent)({
590
672
  name: "FonderieUsersScreen",
591
- props: { client: { type: Object, required: true } },
673
+ props: {
674
+ client: { type: Object, required: true },
675
+ pageSize: { type: Number, default: 50 }
676
+ },
592
677
  setup(props) {
593
- const input = (0, import_vue9.ref)("");
594
- const email = (0, import_vue9.ref)("");
595
- const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_vue_admin8.useAdminUser)(
678
+ const input = (0, import_vue10.ref)("");
679
+ const email = (0, import_vue10.ref)("");
680
+ const selectedId = (0, import_vue10.ref)("");
681
+ const list = (0, import_vue_admin9.useAdminUsers)(props.client, { limit: props.pageSize });
682
+ const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_vue_admin9.useAdminUser)(
596
683
  props.client,
597
- { email }
684
+ { email, id: selectedId }
598
685
  );
599
- const userId = (0, import_vue9.computed)(() => user.value?.id ?? null);
600
- const sessions = (0, import_vue_admin8.useAdminUserSessions)(props.client, userId);
601
- const history = (0, import_vue_admin8.useAdminLoginHistory)(props.client, userId, { limit: 20 });
602
- const yesNo = (v) => (0, import_vue9.h)("span", { style: v ? styles.ok : styles.muted }, v ? "yes" : "no");
603
- const row = (k, v) => (0, import_vue9.h)("tr", [td(k), td(v)]);
686
+ const userId = (0, import_vue10.computed)(() => user.value?.id ?? null);
687
+ const sessions = (0, import_vue_admin9.useAdminUserSessions)(props.client, userId);
688
+ const history = (0, import_vue_admin9.useAdminLoginHistory)(props.client, userId, { limit: 20 });
689
+ const showList = (0, import_vue10.computed)(() => !email.value && !selectedId.value);
690
+ const yesNo = (v) => (0, import_vue10.h)("span", { style: v ? styles.ok : styles.muted }, v ? "yes" : "no");
691
+ const row = (k, v) => (0, import_vue10.h)("tr", [td(k), td(v)]);
692
+ const clear = () => {
693
+ email.value = "";
694
+ selectedId.value = "";
695
+ input.value = "";
696
+ };
604
697
  return () => {
605
698
  const u = user.value;
606
- return (0, import_vue9.h)("div", { style: styles.container }, [
607
- (0, import_vue9.h)("h1", { style: styles.title }, "Users"),
608
- (0, import_vue9.h)(
699
+ return (0, import_vue10.h)("div", { style: styles.container }, [
700
+ (0, import_vue10.h)("h1", { style: styles.title }, "Users"),
701
+ (0, import_vue10.h)(
609
702
  "form",
610
703
  {
611
704
  style: styles.toolbar,
612
705
  onSubmit: (e) => {
613
706
  e.preventDefault();
707
+ selectedId.value = "";
614
708
  email.value = input.value.trim();
615
709
  }
616
710
  },
617
711
  [
618
- (0, import_vue9.h)("input", {
712
+ (0, import_vue10.h)("input", {
619
713
  type: "email",
620
714
  value: input.value,
621
715
  onInput: (e) => input.value = e.target.value,
@@ -623,28 +717,67 @@ var UsersScreen = (0, import_vue9.defineComponent)({
623
717
  style: { ...styles.button, cursor: "text", minWidth: "280px" },
624
718
  "aria-label": "Email"
625
719
  }),
626
- (0, import_vue9.h)(
720
+ (0, import_vue10.h)(
627
721
  "button",
628
722
  { type: "submit", style: styles.button, disabled: isLoading.value },
629
723
  "Look up"
630
- )
724
+ ),
725
+ showList.value ? refreshButton("Refresh", list.isLoading.value, () => void list.refresh()) : refreshButton("\u2190 All users", false, clear)
631
726
  ]
632
727
  ),
633
- error.value ? (0, import_vue9.h)(
728
+ showList.value ? (0, import_vue10.h)("div", [
729
+ list.error.value ? (0, import_vue10.h)("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
730
+ list.users.value.length === 0 && !list.isLoading.value ? (0, import_vue10.h)("p", { style: styles.muted }, "No users yet.") : table(
731
+ ["Email", "Name", "Created", "Status"],
732
+ list.users.value.map(
733
+ (u2) => (0, import_vue10.h)("tr", { key: u2.id }, [
734
+ td(
735
+ (0, import_vue10.h)(
736
+ "button",
737
+ {
738
+ type: "button",
739
+ style: { ...styles.navItem, padding: 0 },
740
+ onClick: () => selectedId.value = u2.id
741
+ },
742
+ u2.email
743
+ )
744
+ ),
745
+ td(
746
+ `${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || (0, import_vue10.h)("span", { style: styles.muted }, "\u2014")
747
+ ),
748
+ td(new Date(u2.createdAt).toLocaleDateString()),
749
+ td(
750
+ u2.suspended ? (0, import_vue10.h)("span", { style: styles.badge }, "suspended") : u2.deletedAt ? (0, import_vue10.h)("span", { style: styles.badge }, "deleted") : (0, import_vue10.h)("span", { style: styles.muted }, "active")
751
+ )
752
+ ])
753
+ )
754
+ ),
755
+ list.isLoading.value ? (0, import_vue10.h)("p", { style: styles.status }, "Loading\u2026") : null,
756
+ list.hasMore.value && !list.isLoading.value ? (0, import_vue10.h)(
757
+ "button",
758
+ {
759
+ type: "button",
760
+ style: { ...styles.button, marginTop: "8px" },
761
+ onClick: () => void list.loadMore()
762
+ },
763
+ "Load more"
764
+ ) : null
765
+ ]) : null,
766
+ error.value ? (0, import_vue10.h)(
634
767
  "p",
635
768
  { style: styles.error, role: "alert" },
636
769
  error.value.status === 404 ? "No user with that email." : error.value.explanation
637
770
  ) : null,
638
771
  u ? [
639
- (0, import_vue9.h)("h2", { style: styles.subtitle }, [
772
+ (0, import_vue10.h)("h2", { style: styles.subtitle }, [
640
773
  u.firstName || u.lastName ? `${u.firstName} ${u.lastName}`.trim() : u.email,
641
774
  " ",
642
- u.suspended ? (0, import_vue9.h)("span", { style: styles.badge }, "suspended") : null,
643
- u.deletedAt ? (0, import_vue9.h)("span", { style: styles.badge }, "deleted") : null
775
+ u.suspended ? (0, import_vue10.h)("span", { style: styles.badge }, "suspended") : null,
776
+ u.deletedAt ? (0, import_vue10.h)("span", { style: styles.badge }, "deleted") : null
644
777
  ]),
645
- (0, import_vue9.h)("table", { style: styles.table }, [
646
- (0, import_vue9.h)("tbody", [
647
- row("id", (0, import_vue9.h)("span", { style: styles.mono }, u.id)),
778
+ (0, import_vue10.h)("table", { style: styles.table }, [
779
+ (0, import_vue10.h)("tbody", [
780
+ row("id", (0, import_vue10.h)("span", { style: styles.mono }, u.id)),
648
781
  row("email", [u.email, " \xB7 verified ", yesNo(u.isEmailVerified)]),
649
782
  row("MFA", yesNo(u.mfaEnabled)),
650
783
  row(
@@ -653,12 +786,12 @@ var UsersScreen = (0, import_vue9.defineComponent)({
653
786
  ),
654
787
  row(
655
788
  "last login",
656
- u.lastLogin ? new Date(u.lastLogin).toLocaleString() : (0, import_vue9.h)("span", { style: styles.muted }, "never")
789
+ u.lastLogin ? new Date(u.lastLogin).toLocaleString() : (0, import_vue10.h)("span", { style: styles.muted }, "never")
657
790
  ),
658
791
  row("created", new Date(u.createdAt).toLocaleString())
659
792
  ])
660
793
  ]),
661
- (0, import_vue9.h)("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
794
+ (0, import_vue10.h)("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
662
795
  u.suspended ? refreshButton("Unsuspend", isLoading.value, () => void unsuspend()) : refreshButton("Suspend", isLoading.value, () => void suspend()),
663
796
  refreshButton(
664
797
  "Sign out everywhere",
@@ -666,17 +799,17 @@ var UsersScreen = (0, import_vue9.defineComponent)({
666
799
  () => void revokeSessions().then(() => sessions.refresh())
667
800
  )
668
801
  ]),
669
- (0, import_vue9.h)("h2", { style: styles.subtitle }, "Live sessions"),
670
- sessions.sessions.value.length === 0 ? (0, import_vue9.h)("p", { style: styles.muted }, "None.") : (0, import_vue9.h)(
802
+ (0, import_vue10.h)("h2", { style: styles.subtitle }, "Live sessions"),
803
+ sessions.sessions.value.length === 0 ? (0, import_vue10.h)("p", { style: styles.muted }, "None.") : (0, import_vue10.h)(
671
804
  "ul",
672
805
  { style: styles.list },
673
806
  sessions.sessions.value.map(
674
- (s) => (0, import_vue9.h)("li", { key: s.id, style: styles.row }, [
675
- (0, import_vue9.h)("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
807
+ (s) => (0, import_vue10.h)("li", { key: s.id, style: styles.row }, [
808
+ (0, import_vue10.h)("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
676
809
  " ",
677
- (0, import_vue9.h)("span", { style: styles.muted }, s.userAgent ?? ""),
810
+ (0, import_vue10.h)("span", { style: styles.muted }, s.userAgent ?? ""),
678
811
  " ",
679
- (0, import_vue9.h)(
812
+ (0, import_vue10.h)(
680
813
  "span",
681
814
  { style: styles.muted },
682
815
  `since ${new Date(s.createdAt).toLocaleString()}`
@@ -684,25 +817,25 @@ var UsersScreen = (0, import_vue9.defineComponent)({
684
817
  ])
685
818
  )
686
819
  ),
687
- (0, import_vue9.h)("h2", { style: styles.subtitle }, "Recent sign-ins"),
688
- history.events.value.length === 0 && !history.isLoading.value ? (0, import_vue9.h)("p", { style: styles.muted }, "None recorded.") : (0, import_vue9.h)(
820
+ (0, import_vue10.h)("h2", { style: styles.subtitle }, "Recent sign-ins"),
821
+ history.events.value.length === 0 && !history.isLoading.value ? (0, import_vue10.h)("p", { style: styles.muted }, "None recorded.") : (0, import_vue10.h)(
689
822
  "ul",
690
823
  { style: styles.list },
691
824
  history.events.value.map(
692
- (e) => (0, import_vue9.h)("li", { key: e.id, style: styles.row }, [
693
- (0, import_vue9.h)(
825
+ (e) => (0, import_vue10.h)("li", { key: e.id, style: styles.row }, [
826
+ (0, import_vue10.h)(
694
827
  "span",
695
828
  { style: e.outcome === "success" ? styles.ok : styles.bad },
696
829
  e.outcome
697
830
  ),
698
831
  " ",
699
- (0, import_vue9.h)("span", { style: styles.muted }, e.method),
832
+ (0, import_vue10.h)("span", { style: styles.muted }, e.method),
700
833
  " ",
701
- (0, import_vue9.h)("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
834
+ (0, import_vue10.h)("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
702
835
  ])
703
836
  )
704
837
  ),
705
- history.hasMore.value && !history.isLoading.value ? (0, import_vue9.h)(
838
+ history.hasMore.value && !history.isLoading.value ? (0, import_vue10.h)(
706
839
  "button",
707
840
  {
708
841
  type: "button",
@@ -718,26 +851,26 @@ var UsersScreen = (0, import_vue9.defineComponent)({
718
851
  });
719
852
 
720
853
  // src/screens/CatalogScreen.ts
721
- var import_vue_admin9 = require("@fonderie/vue-admin");
722
- var import_vue10 = require("vue");
723
- var CatalogScreen = (0, import_vue10.defineComponent)({
854
+ var import_vue_admin10 = require("@fonderie/vue-admin");
855
+ var import_vue11 = require("vue");
856
+ var CatalogScreen = (0, import_vue11.defineComponent)({
724
857
  name: "FonderieCatalogScreen",
725
858
  props: { client: { type: Object, required: true } },
726
859
  setup(props) {
727
- const { catalog, isLoading, error, refresh, deletePlan } = (0, import_vue_admin9.useAdminCatalog)(props.client);
860
+ const { catalog, isLoading, error, refresh, deletePlan } = (0, import_vue_admin10.useAdminCatalog)(props.client);
728
861
  const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
729
862
  return () => {
730
863
  const c = catalog.value;
731
- return (0, import_vue10.h)("div", { style: styles.container }, [
732
- (0, import_vue10.h)("div", { style: styles.toolbar }, [
733
- (0, import_vue10.h)("h1", { style: { ...styles.title, marginBottom: 0 } }, "Catalog"),
864
+ return (0, import_vue11.h)("div", { style: styles.container }, [
865
+ (0, import_vue11.h)("div", { style: styles.toolbar }, [
866
+ (0, import_vue11.h)("h1", { style: { ...styles.title, marginBottom: 0 } }, "Catalog"),
734
867
  refreshButton("Refresh", isLoading.value, () => void refresh())
735
868
  ]),
736
- error.value ? (0, import_vue10.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
737
- isLoading.value && !c ? (0, import_vue10.h)("p", { style: styles.status }, "Loading\u2026") : null,
869
+ error.value ? (0, import_vue11.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
870
+ isLoading.value && !c ? (0, import_vue11.h)("p", { style: styles.status }, "Loading\u2026") : null,
738
871
  c ? [
739
- (0, import_vue10.h)("h2", { style: styles.subtitle }, "Configured (code)"),
740
- (0, import_vue10.h)(
872
+ (0, import_vue11.h)("h2", { style: styles.subtitle }, "Configured (code)"),
873
+ (0, import_vue11.h)(
741
874
  "pre",
742
875
  {
743
876
  style: {
@@ -749,14 +882,14 @@ var CatalogScreen = (0, import_vue10.defineComponent)({
749
882
  },
750
883
  JSON.stringify(c.configured, null, 2)
751
884
  ),
752
- (0, import_vue10.h)("h2", { style: styles.subtitle }, "Stored (database)"),
753
- c.stored.length === 0 ? (0, import_vue10.h)("p", { style: styles.muted }, "No stored plans.") : table(
885
+ (0, import_vue11.h)("h2", { style: styles.subtitle }, "Stored (database)"),
886
+ c.stored.length === 0 ? (0, import_vue11.h)("p", { style: styles.muted }, "No stored plans.") : table(
754
887
  ["Plan", "Tier", "Seats", "Monthly", "Yearly", "Trial", ""],
755
888
  c.stored.map(
756
- (p) => (0, import_vue10.h)("tr", { key: p.id }, [
889
+ (p) => (0, import_vue11.h)("tr", { key: p.id }, [
757
890
  td([
758
- (0, import_vue10.h)("strong", p.name),
759
- p.description ? (0, import_vue10.h)("div", { style: styles.muted }, p.description) : ""
891
+ (0, import_vue11.h)("strong", p.name),
892
+ p.description ? (0, import_vue11.h)("div", { style: styles.muted }, p.description) : ""
760
893
  ]),
761
894
  td(String(p.tier)),
762
895
  td(p.seats == null ? "\u221E" : String(p.seats)),
@@ -764,7 +897,7 @@ var CatalogScreen = (0, import_vue10.defineComponent)({
764
897
  td(money(p.pricing?.yearly)),
765
898
  td(p.trialDays ? `${p.trialDays} d` : "\u2014"),
766
899
  td(
767
- (0, import_vue10.h)(
900
+ (0, import_vue11.h)(
768
901
  "button",
769
902
  {
770
903
  type: "button",
@@ -788,20 +921,30 @@ var CatalogScreen = (0, import_vue10.defineComponent)({
788
921
  });
789
922
 
790
923
  // src/screens/SubscriberScreen.ts
791
- var import_vue_admin10 = require("@fonderie/vue-admin");
792
- var import_vue11 = require("vue");
793
- var SubscriberScreen = (0, import_vue11.defineComponent)({
924
+ var import_vue_admin11 = require("@fonderie/vue-admin");
925
+ var import_vue12 = require("vue");
926
+ var SubscriberScreen = (0, import_vue12.defineComponent)({
794
927
  name: "FonderieSubscriberScreen",
795
- props: { client: { type: Object, required: true } },
928
+ props: {
929
+ client: { type: Object, required: true },
930
+ pageSize: { type: Number, default: 50 }
931
+ },
796
932
  setup(props) {
797
- const type = (0, import_vue11.ref)("user");
798
- const idInput = (0, import_vue11.ref)("");
799
- const subscriber = (0, import_vue11.ref)(null);
800
- const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_vue_admin10.useAdminSubscriber)(props.client, subscriber, { limit: 20 });
801
- const amount = (0, import_vue11.ref)("");
802
- const note = (0, import_vue11.ref)("");
803
- const granted = (0, import_vue11.ref)(null);
804
- const input = (model, placeholder, extra = {}) => (0, import_vue11.h)("input", {
933
+ const type = (0, import_vue12.ref)("user");
934
+ const idInput = (0, import_vue12.ref)("");
935
+ const subscriber = (0, import_vue12.ref)(null);
936
+ const list = (0, import_vue_admin11.useAdminSubscribers)(props.client, { limit: props.pageSize });
937
+ const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_vue_admin11.useAdminSubscriber)(props.client, subscriber, { limit: 20 });
938
+ const open = (t, id) => {
939
+ granted.value = null;
940
+ type.value = t;
941
+ idInput.value = id;
942
+ subscriber.value = { type: t, id };
943
+ };
944
+ const amount = (0, import_vue12.ref)("");
945
+ const note = (0, import_vue12.ref)("");
946
+ const granted = (0, import_vue12.ref)(null);
947
+ const input = (model, placeholder, extra = {}) => (0, import_vue12.h)("input", {
805
948
  value: model.value,
806
949
  onInput: (e) => model.value = e.target.value,
807
950
  placeholder,
@@ -811,9 +954,9 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
811
954
  return () => {
812
955
  const s = subscription.value;
813
956
  const w = wallet.value;
814
- return (0, import_vue11.h)("div", { style: styles.container }, [
815
- (0, import_vue11.h)("h1", { style: styles.title }, "Subscriber"),
816
- (0, import_vue11.h)(
957
+ return (0, import_vue12.h)("div", { style: styles.container }, [
958
+ (0, import_vue12.h)("h1", { style: styles.title }, "Subscriber"),
959
+ (0, import_vue12.h)(
817
960
  "form",
818
961
  {
819
962
  style: styles.toolbar,
@@ -824,7 +967,7 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
824
967
  }
825
968
  },
826
969
  [
827
- (0, import_vue11.h)(
970
+ (0, import_vue12.h)(
828
971
  "select",
829
972
  {
830
973
  value: type.value,
@@ -833,49 +976,109 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
833
976
  "aria-label": "Type"
834
977
  },
835
978
  [
836
- (0, import_vue11.h)("option", { value: "user" }, "user"),
837
- (0, import_vue11.h)("option", { value: "workspace" }, "workspace")
979
+ (0, import_vue12.h)("option", { value: "user" }, "user"),
980
+ (0, import_vue12.h)("option", { value: "workspace" }, "workspace")
838
981
  ]
839
982
  ),
840
983
  input(idInput, "subscriber id", { minWidth: "280px" }),
841
- (0, import_vue11.h)(
984
+ (0, import_vue12.h)(
842
985
  "button",
843
986
  { type: "submit", style: styles.button, disabled: isLoading.value },
844
987
  "Look up"
988
+ ),
989
+ subscriber.value ? (0, import_vue12.h)(
990
+ "button",
991
+ {
992
+ type: "button",
993
+ style: styles.button,
994
+ onClick: () => {
995
+ subscriber.value = null;
996
+ idInput.value = "";
997
+ granted.value = null;
998
+ }
999
+ },
1000
+ "\u2190 All subscribers"
1001
+ ) : (0, import_vue12.h)(
1002
+ "button",
1003
+ {
1004
+ type: "button",
1005
+ style: styles.button,
1006
+ disabled: list.isLoading.value,
1007
+ onClick: () => void list.refresh()
1008
+ },
1009
+ "Refresh"
845
1010
  )
846
1011
  ]
847
1012
  ),
848
- error.value ? (0, import_vue11.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
1013
+ !subscriber.value ? (0, import_vue12.h)("div", [
1014
+ list.error.value ? (0, import_vue12.h)("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
1015
+ list.subscriptions.value.length === 0 && !list.isLoading.value ? (0, import_vue12.h)("p", { style: styles.muted }, "No subscribers yet.") : table(
1016
+ ["Subscriber", "Plan", "Status", "Renews"],
1017
+ list.subscriptions.value.map(
1018
+ (sub) => (0, import_vue12.h)("tr", { key: sub.id }, [
1019
+ td(
1020
+ (0, import_vue12.h)(
1021
+ "button",
1022
+ {
1023
+ type: "button",
1024
+ style: { ...styles.navItem, padding: 0, ...styles.mono },
1025
+ onClick: () => open(sub.subscriberType, sub.subscriberId)
1026
+ },
1027
+ `${sub.subscriberType}/${sub.subscriberId}`
1028
+ )
1029
+ ),
1030
+ td(`${sub.plan} \xB7 ${sub.interval}`),
1031
+ td(
1032
+ sub.cancelAtPeriodEnd ? [sub.status, " ", (0, import_vue12.h)("span", { style: styles.badge }, "cancels")] : sub.status
1033
+ ),
1034
+ td(
1035
+ sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : (0, import_vue12.h)("span", { style: styles.muted }, "\u2014")
1036
+ )
1037
+ ])
1038
+ )
1039
+ ),
1040
+ list.isLoading.value ? (0, import_vue12.h)("p", { style: styles.status }, "Loading\u2026") : null,
1041
+ list.hasMore.value && !list.isLoading.value ? (0, import_vue12.h)(
1042
+ "button",
1043
+ {
1044
+ type: "button",
1045
+ style: { ...styles.button, marginTop: "8px" },
1046
+ onClick: () => void list.loadMore()
1047
+ },
1048
+ "Load more"
1049
+ ) : null
1050
+ ]) : null,
1051
+ error.value ? (0, import_vue12.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
849
1052
  subscriber.value && !isLoading.value ? [
850
- (0, import_vue11.h)("h2", { style: styles.subtitle }, "Subscription"),
851
- s ? (0, import_vue11.h)("table", { style: styles.table }, [
852
- (0, import_vue11.h)("tbody", [
853
- (0, import_vue11.h)("tr", [td("plan"), td([(0, import_vue11.h)("strong", s.plan), ` \xB7 ${s.interval}`])]),
854
- (0, import_vue11.h)("tr", [
1053
+ (0, import_vue12.h)("h2", { style: styles.subtitle }, "Subscription"),
1054
+ s ? (0, import_vue12.h)("table", { style: styles.table }, [
1055
+ (0, import_vue12.h)("tbody", [
1056
+ (0, import_vue12.h)("tr", [td("plan"), td([(0, import_vue12.h)("strong", s.plan), ` \xB7 ${s.interval}`])]),
1057
+ (0, import_vue12.h)("tr", [
855
1058
  td("status"),
856
1059
  td(`${s.status}${s.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""}`)
857
1060
  ]),
858
- (0, import_vue11.h)("tr", [
1061
+ (0, import_vue12.h)("tr", [
859
1062
  td("period"),
860
1063
  td(
861
1064
  `${s.currentPeriodStart ? new Date(s.currentPeriodStart).toLocaleDateString() : "\u2014"} \u2192 ${s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : "\u2014"}`
862
1065
  )
863
1066
  ]),
864
- (0, import_vue11.h)("tr", [td("provider"), td(s.providerSubscriptionId ?? "\u2014", styles.mono)])
1067
+ (0, import_vue12.h)("tr", [td("provider"), td(s.providerSubscriptionId ?? "\u2014", styles.mono)])
865
1068
  ])
866
- ]) : (0, import_vue11.h)("p", { style: styles.muted }, "No subscription."),
867
- (0, import_vue11.h)("h2", { style: styles.subtitle }, "Wallet"),
1069
+ ]) : (0, import_vue12.h)("p", { style: styles.muted }, "No subscription."),
1070
+ (0, import_vue12.h)("h2", { style: styles.subtitle }, "Wallet"),
868
1071
  w ? [
869
- (0, import_vue11.h)("p", [
870
- (0, import_vue11.h)("strong", w.balance),
1072
+ (0, import_vue12.h)("p", [
1073
+ (0, import_vue12.h)("strong", w.balance),
871
1074
  ` ${w.currency} `,
872
- (0, import_vue11.h)(
1075
+ (0, import_vue12.h)(
873
1076
  "span",
874
1077
  { style: styles.muted },
875
1078
  `(granted ${w.granted ?? "0"} \xB7 purchased ${w.purchased ?? "0"}) \xB7 minor units, precision ${w.precision}`
876
1079
  )
877
1080
  ]),
878
- (0, import_vue11.h)(
1081
+ (0, import_vue12.h)(
879
1082
  "form",
880
1083
  {
881
1084
  style: styles.toolbar,
@@ -900,7 +1103,7 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
900
1103
  [
901
1104
  input(amount, "amount (minor units)"),
902
1105
  input(note, "reason (optional)", { minWidth: "220px" }),
903
- (0, import_vue11.h)(
1106
+ (0, import_vue12.h)(
904
1107
  "button",
905
1108
  {
906
1109
  type: "submit",
@@ -909,14 +1112,14 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
909
1112
  },
910
1113
  "Grant"
911
1114
  ),
912
- granted.value ? (0, import_vue11.h)("span", { style: styles.ok }, granted.value) : null
1115
+ granted.value ? (0, import_vue12.h)("span", { style: styles.ok }, granted.value) : null
913
1116
  ]
914
1117
  ),
915
- (0, import_vue11.h)("h2", { style: styles.subtitle }, "Ledger"),
916
- ledger.value.length === 0 ? (0, import_vue11.h)("p", { style: styles.muted }, "Nothing moved yet.") : table(
1118
+ (0, import_vue12.h)("h2", { style: styles.subtitle }, "Ledger"),
1119
+ ledger.value.length === 0 ? (0, import_vue12.h)("p", { style: styles.muted }, "Nothing moved yet.") : table(
917
1120
  ["When", "Kind", "Amount", "Note"],
918
1121
  ledger.value.map(
919
- (t) => (0, import_vue11.h)("tr", { key: t.id }, [
1122
+ (t) => (0, import_vue12.h)("tr", { key: t.id }, [
920
1123
  td(new Date(t.createdAt).toLocaleString(), styles.muted),
921
1124
  td(t.type),
922
1125
  td(t.amount, styles.mono),
@@ -924,7 +1127,7 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
924
1127
  ])
925
1128
  )
926
1129
  ),
927
- hasMoreLedger.value ? (0, import_vue11.h)(
1130
+ hasMoreLedger.value ? (0, import_vue12.h)(
928
1131
  "button",
929
1132
  {
930
1133
  type: "button",
@@ -933,7 +1136,7 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
933
1136
  },
934
1137
  "Load more"
935
1138
  ) : null
936
- ] : (0, import_vue11.h)(
1139
+ ] : (0, import_vue12.h)(
937
1140
  "p",
938
1141
  { style: styles.muted },
939
1142
  "No wallet \u2014 billing has no wallet configuration."
@@ -945,31 +1148,31 @@ var SubscriberScreen = (0, import_vue11.defineComponent)({
945
1148
  });
946
1149
 
947
1150
  // 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)({
1151
+ var import_vue_admin12 = require("@fonderie/vue-admin");
1152
+ var import_vue13 = require("vue");
1153
+ var AuditScreen = (0, import_vue13.defineComponent)({
951
1154
  name: "FonderieAuditScreen",
952
1155
  props: {
953
1156
  client: { type: Object, required: true },
954
1157
  pageSize: { type: Number, default: 50 }
955
1158
  },
956
1159
  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)(
1160
+ const draft = (0, import_vue13.ref)({ workspaceId: "", type: "", actorId: "" });
1161
+ const query = (0, import_vue13.ref)({ limit: props.pageSize });
1162
+ const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_vue_admin12.useAdminAudit)(
960
1163
  props.client,
961
1164
  query
962
1165
  );
963
- const field = (key, placeholder) => (0, import_vue12.h)("input", {
1166
+ const field = (key, placeholder) => (0, import_vue13.h)("input", {
964
1167
  value: draft.value[key],
965
1168
  onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
966
1169
  placeholder,
967
1170
  style: { ...styles.button, cursor: "text" },
968
1171
  "aria-label": placeholder
969
1172
  });
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)(
1173
+ return () => (0, import_vue13.h)("div", { style: styles.container }, [
1174
+ (0, import_vue13.h)("h1", { style: styles.title }, "Audit"),
1175
+ (0, import_vue13.h)(
973
1176
  "form",
974
1177
  {
975
1178
  style: styles.toolbar,
@@ -988,7 +1191,7 @@ var AuditScreen = (0, import_vue12.defineComponent)({
988
1191
  field("workspaceId", "workspace id (all if empty)"),
989
1192
  field("type", "event type"),
990
1193
  field("actorId", "actor id"),
991
- (0, import_vue12.h)(
1194
+ (0, import_vue13.h)(
992
1195
  "button",
993
1196
  { type: "submit", style: styles.button, disabled: isLoading.value },
994
1197
  "Filter"
@@ -996,11 +1199,11 @@ var AuditScreen = (0, import_vue12.defineComponent)({
996
1199
  refreshButton("Refresh", isLoading.value, () => void refresh())
997
1200
  ]
998
1201
  ),
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(
1202
+ error.value ? (0, import_vue13.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
1203
+ events.value.length === 0 && !isLoading.value ? (0, import_vue13.h)("p", { style: styles.muted }, "Nothing recorded for this filter.") : table(
1001
1204
  ["When", "Type", "Workspace", "Actor", "Request"],
1002
1205
  events.value.map(
1003
- (e) => (0, import_vue12.h)("tr", { key: e.id }, [
1206
+ (e) => (0, import_vue13.h)("tr", { key: e.id }, [
1004
1207
  td(new Date(e.createdAt).toLocaleString(), styles.muted),
1005
1208
  td(e.type, styles.mono),
1006
1209
  td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
@@ -1009,8 +1212,8 @@ var AuditScreen = (0, import_vue12.defineComponent)({
1009
1212
  ])
1010
1213
  )
1011
1214
  ),
1012
- isLoading.value ? (0, import_vue12.h)("p", { style: styles.status }, "Loading\u2026") : null,
1013
- hasMore.value && !isLoading.value ? (0, import_vue12.h)(
1215
+ isLoading.value ? (0, import_vue13.h)("p", { style: styles.status }, "Loading\u2026") : null,
1216
+ hasMore.value && !isLoading.value ? (0, import_vue13.h)(
1014
1217
  "button",
1015
1218
  {
1016
1219
  type: "button",
@@ -1050,11 +1253,12 @@ var NAV = [
1050
1253
  items: [
1051
1254
  { page: "audit", label: "Audit", needs: "audit" },
1052
1255
  { page: "log", label: "Admin log" },
1053
- { page: "tokens", label: "Access" }
1256
+ { page: "tokens", label: "Access" },
1257
+ { page: "migrations", label: "Migrations" }
1054
1258
  ]
1055
1259
  }
1056
1260
  ];
1057
- var AdminShell = (0, import_vue13.defineComponent)({
1261
+ var AdminShell = (0, import_vue14.defineComponent)({
1058
1262
  name: "FonderieAdminShell",
1059
1263
  props: {
1060
1264
  client: { type: Object, required: true },
@@ -1075,15 +1279,15 @@ var AdminShell = (0, import_vue13.defineComponent)({
1075
1279
  },
1076
1280
  emits: { navigate: (_page) => true },
1077
1281
  setup(props, { emit }) {
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);
1282
+ const own = (0, import_vue14.ref)("attention");
1283
+ const current = (0, import_vue14.computed)(() => props.page ?? own.value);
1284
+ const editing = (0, import_vue14.ref)(null);
1081
1285
  const go = (p) => {
1082
1286
  editing.value = null;
1083
1287
  emit("navigate", p);
1084
1288
  if (props.page === void 0) own.value = p;
1085
1289
  };
1086
- const back = () => (0, import_vue13.h)(
1290
+ const back = () => (0, import_vue14.h)(
1087
1291
  "button",
1088
1292
  { type: "button", style: styles.button, onClick: () => editing.value = null },
1089
1293
  "\u2190 Back"
@@ -1092,43 +1296,45 @@ var AdminShell = (0, import_vue13.defineComponent)({
1092
1296
  const c = props.client;
1093
1297
  switch (current.value) {
1094
1298
  case "attention":
1095
- return (0, import_vue13.h)(AttentionScreen, { client: c });
1299
+ return (0, import_vue14.h)(AttentionScreen, { client: c });
1096
1300
  case "modules":
1097
- return (0, import_vue13.h)(ModulesScreen, { client: c });
1301
+ return (0, import_vue14.h)(ModulesScreen, { client: c });
1098
1302
  case "doctor":
1099
- return (0, import_vue13.h)(DoctorScreen, { client: c });
1303
+ return (0, import_vue14.h)(DoctorScreen, { client: c });
1100
1304
  case "config":
1101
- return (0, import_vue13.h)(ConfigScreen, { client: c });
1305
+ return (0, import_vue14.h)(ConfigScreen, { client: c });
1102
1306
  case "routes":
1103
- return (0, import_vue13.h)(RoutesScreen, { client: c });
1307
+ return (0, import_vue14.h)(RoutesScreen, { client: c });
1104
1308
  case "tokens":
1105
- return (0, import_vue13.h)(TokensScreen, { client: c });
1309
+ return (0, import_vue14.h)(TokensScreen, { client: c });
1310
+ case "migrations":
1311
+ return (0, import_vue14.h)(MigrationsScreen, { client: c });
1106
1312
  case "log":
1107
- return (0, import_vue13.h)(AdminLogScreen, { client: c });
1313
+ return (0, import_vue14.h)(AdminLogScreen, { client: c });
1108
1314
  case "catalog":
1109
- return props.billingClient ? (0, import_vue13.h)(CatalogScreen, { client: props.billingClient }) : (0, import_vue13.h)(
1315
+ return props.billingClient ? (0, import_vue14.h)(CatalogScreen, { client: props.billingClient }) : (0, import_vue14.h)(
1110
1316
  "p",
1111
1317
  { style: styles.status },
1112
1318
  "Pass a BillingAdminClient to see the catalog here."
1113
1319
  );
1114
1320
  case "subscriber":
1115
- return props.billingClient ? (0, import_vue13.h)(SubscriberScreen, { client: props.billingClient }) : (0, import_vue13.h)(
1321
+ return props.billingClient ? (0, import_vue14.h)(SubscriberScreen, { client: props.billingClient }) : (0, import_vue14.h)(
1116
1322
  "p",
1117
1323
  { style: styles.status },
1118
1324
  "Pass a BillingAdminClient to look up subscribers here."
1119
1325
  );
1120
1326
  case "audit":
1121
- return props.auditClient ? (0, import_vue13.h)(AuditScreen, { client: props.auditClient }) : (0, import_vue13.h)(
1327
+ return props.auditClient ? (0, import_vue14.h)(AuditScreen, { client: props.auditClient }) : (0, import_vue14.h)(
1122
1328
  "p",
1123
1329
  { style: styles.status },
1124
1330
  "Pass an AuditAdminClient to see the audit trail here."
1125
1331
  );
1126
1332
  case "users":
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.");
1333
+ return props.authClient ? (0, import_vue14.h)(UsersScreen, { client: props.authClient }) : (0, import_vue14.h)("p", { style: styles.status }, "Pass an AuthAdminClient to look up users here.");
1128
1334
  case "settings": {
1129
1335
  const cc = props.configClient;
1130
1336
  if (!cc)
1131
- return (0, import_vue13.h)(
1337
+ return (0, import_vue14.h)(
1132
1338
  "p",
1133
1339
  { style: styles.status },
1134
1340
  "Pass a ConfigAdminClient to manage config and secrets here."
@@ -1137,7 +1343,7 @@ var AdminShell = (0, import_vue13.defineComponent)({
1137
1343
  if (e && e.kind !== "template")
1138
1344
  return [
1139
1345
  back(),
1140
- (0, import_vue13.h)(import_vue_config_admin_screens.ConfigEditorScreen, {
1346
+ (0, import_vue14.h)(import_vue_config_admin_screens.ConfigEditorScreen, {
1141
1347
  client: cc,
1142
1348
  kind: e.kind,
1143
1349
  configKey: e.key,
@@ -1145,7 +1351,7 @@ var AdminShell = (0, import_vue13.defineComponent)({
1145
1351
  onSaved: () => editing.value = null
1146
1352
  })
1147
1353
  ];
1148
- return (0, import_vue13.h)(import_vue_config_admin_screens.ConfigListScreen, {
1354
+ return (0, import_vue14.h)(import_vue_config_admin_screens.ConfigListScreen, {
1149
1355
  client: cc,
1150
1356
  ...props.environment !== void 0 ? { environment: props.environment } : {},
1151
1357
  "onSelect-config": (key) => editing.value = { kind: "config", key },
@@ -1155,7 +1361,7 @@ var AdminShell = (0, import_vue13.defineComponent)({
1155
1361
  case "templates": {
1156
1362
  const kc = props.courierClient;
1157
1363
  if (!kc)
1158
- return (0, import_vue13.h)(
1364
+ return (0, import_vue14.h)(
1159
1365
  "p",
1160
1366
  { style: styles.status },
1161
1367
  "Pass a CourierAdminClient to manage templates here."
@@ -1164,21 +1370,21 @@ var AdminShell = (0, import_vue13.defineComponent)({
1164
1370
  if (e?.kind === "template")
1165
1371
  return [
1166
1372
  back(),
1167
- (0, import_vue13.h)(import_vue_courier_admin_screens.TemplateEditorScreen, {
1373
+ (0, import_vue14.h)(import_vue_courier_admin_screens.TemplateEditorScreen, {
1168
1374
  client: kc,
1169
1375
  type: e.type,
1170
1376
  onSaved: () => editing.value = null
1171
1377
  })
1172
1378
  ];
1173
- return (0, import_vue13.h)(import_vue_courier_admin_screens.TemplateListScreen, {
1379
+ return (0, import_vue14.h)(import_vue_courier_admin_screens.TemplateListScreen, {
1174
1380
  client: kc,
1175
1381
  "onSelect-template": (t) => editing.value = { kind: "template", type: t.type }
1176
1382
  });
1177
1383
  }
1178
1384
  }
1179
1385
  };
1180
- return () => (0, import_vue13.h)("div", { style: styles.shell }, [
1181
- (0, import_vue13.h)(
1386
+ return () => (0, import_vue14.h)("div", { style: styles.shell }, [
1387
+ (0, import_vue14.h)(
1182
1388
  "nav",
1183
1389
  { style: styles.nav, "aria-label": "Admin" },
1184
1390
  NAV.map((g) => {
@@ -1186,10 +1392,10 @@ var AdminShell = (0, import_vue13.defineComponent)({
1186
1392
  (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)
1187
1393
  );
1188
1394
  if (items.length === 0) return null;
1189
- return (0, import_vue13.h)("div", { key: g.group }, [
1190
- (0, import_vue13.h)("div", { style: styles.navGroup }, g.group),
1395
+ return (0, import_vue14.h)("div", { key: g.group }, [
1396
+ (0, import_vue14.h)("div", { style: styles.navGroup }, g.group),
1191
1397
  ...items.map(
1192
- (i) => (0, import_vue13.h)(
1398
+ (i) => (0, import_vue14.h)(
1193
1399
  "button",
1194
1400
  {
1195
1401
  key: i.page,
@@ -1207,7 +1413,7 @@ var AdminShell = (0, import_vue13.defineComponent)({
1207
1413
  ]);
1208
1414
  })
1209
1415
  ),
1210
- (0, import_vue13.h)("main", { style: styles.main }, body())
1416
+ (0, import_vue14.h)("main", { style: styles.main }, body())
1211
1417
  ]);
1212
1418
  }
1213
1419
  });
@@ -1220,6 +1426,7 @@ var AdminShell = (0, import_vue13.defineComponent)({
1220
1426
  CatalogScreen,
1221
1427
  ConfigScreen,
1222
1428
  DoctorScreen,
1429
+ MigrationsScreen,
1223
1430
  ModulesScreen,
1224
1431
  RoutesScreen,
1225
1432
  SubscriberScreen,