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