@fonderie/vue-admin-screens 1.1.0 → 1.2.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 defineComponent13, h as h15, onBeforeUnmount, onMounted, ref as ref6 } from "vue";
4
+ import { computed as computed2, defineComponent as defineComponent14, h as h16, onBeforeUnmount, onMounted, ref as ref7 } from "vue";
5
5
 
6
6
  // src/styles.ts
7
7
  var t = {
@@ -1167,42 +1167,319 @@ var MigrationsScreen = defineComponent7({
1167
1167
  }
1168
1168
  });
1169
1169
 
1170
+ // src/screens/OperatorsScreen.ts
1171
+ import { useAdminOperators } from "@fonderie/vue-admin";
1172
+ import { defineComponent as defineComponent8, h as h10, ref as ref2 } from "vue";
1173
+ var LEVELS = [
1174
+ { label: "Read only", scopes: ["read"], hint: "look around, change nothing" },
1175
+ { label: "Editor", scopes: ["read", "write"], hint: "change config, templates, users" },
1176
+ {
1177
+ label: "Owner",
1178
+ scopes: ["read", "write", "secrets"],
1179
+ hint: "secrets, tokens and operators too"
1180
+ }
1181
+ ];
1182
+ var levelOf = (scopes) => scopes.includes("secrets") ? "Owner" : scopes.includes("write") ? "Editor" : "Read only";
1183
+ var scopesFor = (label) => LEVELS.find((l) => l.label === label)?.scopes ?? ["read"];
1184
+ var small = { height: "28px" };
1185
+ var OperatorsScreen = defineComponent8({
1186
+ name: "FonderieOperatorsScreen",
1187
+ props: {
1188
+ client: { type: Object, required: true },
1189
+ // The signed-in operator's email: their own row offers no disable or
1190
+ // recovery (the server refuses both for yourself anyway).
1191
+ me: { type: String, default: void 0 }
1192
+ },
1193
+ setup(props) {
1194
+ const { report, isLoading, error, invite, recover, update, revokeLink } = useAdminOperators(
1195
+ props.client
1196
+ );
1197
+ const email = ref2("");
1198
+ const level = ref2("Editor");
1199
+ const minted = ref2(null);
1200
+ const copied = ref2(false);
1201
+ const status = (o) => o.disabledAt ? pill("neutral", "disabled") : o.locked ? pill("warn", "locked") : !o.enrolled ? pill("warn", "setting up") : pill("ok", "active");
1202
+ const mintedNotice = (link) => {
1203
+ const url = `${window.location.origin}${link.url}`;
1204
+ return h10(
1205
+ "div",
1206
+ {
1207
+ style: {
1208
+ ...styles.notice,
1209
+ background: "color-mix(in srgb, var(--fonderie-accent,#00d294) 10%, transparent)",
1210
+ borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
1211
+ }
1212
+ },
1213
+ [
1214
+ h10("strong", `Send this link to ${link.email}. It is shown once.`),
1215
+ ` It works once and expires ${new Date(link.expiresAt).toLocaleString()}.`,
1216
+ h10(
1217
+ "div",
1218
+ { style: { display: "flex", gap: "8px", marginTop: "8px", alignItems: "center" } },
1219
+ [
1220
+ h10(
1221
+ "code",
1222
+ { style: { ...styles.code, flex: 1, padding: "7px 10px", wordBreak: "break-all" } },
1223
+ url
1224
+ ),
1225
+ h10(
1226
+ "button",
1227
+ {
1228
+ type: "button",
1229
+ style: styles.button,
1230
+ onClick: () => {
1231
+ void navigator.clipboard?.writeText(url);
1232
+ copied.value = true;
1233
+ }
1234
+ },
1235
+ copied.value ? "Copied" : "Copy"
1236
+ ),
1237
+ h10(
1238
+ "button",
1239
+ {
1240
+ type: "button",
1241
+ style: styles.buttonGhost,
1242
+ "aria-label": "Dismiss",
1243
+ onClick: () => {
1244
+ minted.value = null;
1245
+ copied.value = false;
1246
+ }
1247
+ },
1248
+ [icon("close", 14)]
1249
+ )
1250
+ ]
1251
+ )
1252
+ ]
1253
+ );
1254
+ };
1255
+ const setMinted = (l) => {
1256
+ copied.value = false;
1257
+ minted.value = l;
1258
+ };
1259
+ return () => {
1260
+ const r = report.value;
1261
+ const e = error.value;
1262
+ return h10("div", { style: styles.container }, [
1263
+ pageHeader(
1264
+ "Operators",
1265
+ "The people who can sign in to this console. Each one uses a password and an authenticator app; there is no sign-up."
1266
+ ),
1267
+ e ? h10(
1268
+ "p",
1269
+ { style: styles.error, role: "alert" },
1270
+ e.status === 403 && e.reason === "FORBIDDEN" ? "Managing operators needs the Owner level." : e.explanation
1271
+ ) : null,
1272
+ minted.value ? mintedNotice(minted.value) : null,
1273
+ h10(
1274
+ "form",
1275
+ {
1276
+ style: {
1277
+ ...styles.card,
1278
+ display: "flex",
1279
+ gap: "8px",
1280
+ alignItems: "center",
1281
+ flexWrap: "wrap",
1282
+ marginBottom: "20px"
1283
+ },
1284
+ onSubmit: (ev) => {
1285
+ ev.preventDefault();
1286
+ void invite({ email: email.value.trim(), scopes: scopesFor(level.value) }).then((l) => {
1287
+ setMinted(l);
1288
+ email.value = "";
1289
+ }).catch(() => {
1290
+ });
1291
+ }
1292
+ },
1293
+ [
1294
+ icon("users", 16),
1295
+ h10("strong", { style: { fontSize: "13.5px", marginRight: "4px" } }, "Invite"),
1296
+ h10("input", {
1297
+ type: "email",
1298
+ required: true,
1299
+ value: email.value,
1300
+ onInput: (ev) => email.value = ev.target.value,
1301
+ placeholder: "teammate@company.com",
1302
+ style: { ...styles.input, flex: 1, minWidth: "220px" },
1303
+ "aria-label": "Email to invite"
1304
+ }),
1305
+ h10(
1306
+ "select",
1307
+ {
1308
+ value: level.value,
1309
+ onChange: (ev) => level.value = ev.target.value,
1310
+ style: styles.input,
1311
+ "aria-label": "Access level"
1312
+ },
1313
+ LEVELS.map(
1314
+ (l) => h10("option", { key: l.label, value: l.label }, `${l.label} \u2014 ${l.hint}`)
1315
+ )
1316
+ ),
1317
+ h10(
1318
+ "button",
1319
+ { type: "submit", style: styles.buttonPrimary, disabled: !email.value.trim() },
1320
+ [icon("plus", 14), "Create invite link"]
1321
+ )
1322
+ ]
1323
+ ),
1324
+ isLoading.value && !r ? h10("p", { style: styles.status }, "Loading\u2026") : r && r.operators.length === 0 ? empty("No operators yet", void 0, "users") : r ? table(
1325
+ ["Operator", "Access", "Status", "Last sign-in", ""],
1326
+ r.operators.map((o) => {
1327
+ const mine = o.email === props.me;
1328
+ return h10("tr", { key: o.id }, [
1329
+ td([
1330
+ h10("div", { style: { fontWeight: 600 } }, o.name || o.email),
1331
+ o.name ? h10("div", { style: styles.muted }, o.email) : null
1332
+ ]),
1333
+ td(
1334
+ h10(
1335
+ "select",
1336
+ {
1337
+ value: levelOf(o.scopes),
1338
+ disabled: mine,
1339
+ onChange: (ev) => void update(o.id, {
1340
+ scopes: scopesFor(ev.target.value)
1341
+ }).catch(() => {
1342
+ }),
1343
+ style: { ...styles.input, ...small, fontSize: "12.5px" },
1344
+ "aria-label": `Access level for ${o.email}`
1345
+ },
1346
+ LEVELS.map((l) => h10("option", { key: l.label, value: l.label }, l.label))
1347
+ )
1348
+ ),
1349
+ td([
1350
+ status(o),
1351
+ o.enrolled && o.backupCodesLeft <= 2 && !o.disabledAt ? h10(
1352
+ "div",
1353
+ { style: { ...styles.muted, marginTop: "4px" } },
1354
+ `${o.backupCodesLeft} backup code(s) left`
1355
+ ) : null
1356
+ ]),
1357
+ td(o.lastLoginAt ? new Date(o.lastLoginAt).toLocaleString() : "never", {
1358
+ ...styles.muted,
1359
+ whiteSpace: "nowrap"
1360
+ }),
1361
+ td(
1362
+ mine ? h10("span", { style: styles.muted }, "you") : [
1363
+ h10(
1364
+ "button",
1365
+ {
1366
+ type: "button",
1367
+ style: { ...styles.button, ...small, marginRight: "6px" },
1368
+ onClick: () => {
1369
+ if (window.confirm(
1370
+ `Create a recovery link for ${o.email}? It signs them out everywhere; the link sets a new password and a new authenticator.`
1371
+ ))
1372
+ void recover(o.id).then(setMinted).catch(() => {
1373
+ });
1374
+ }
1375
+ },
1376
+ "Recovery link"
1377
+ ),
1378
+ h10(
1379
+ "button",
1380
+ {
1381
+ type: "button",
1382
+ style: {
1383
+ ...o.disabledAt ? styles.button : styles.buttonDanger,
1384
+ ...small
1385
+ },
1386
+ onClick: () => {
1387
+ if (o.disabledAt || window.confirm(
1388
+ `Disable ${o.email}? They are signed out immediately.`
1389
+ ))
1390
+ void update(o.id, { disabled: !o.disabledAt }).catch(
1391
+ () => {
1392
+ }
1393
+ );
1394
+ }
1395
+ },
1396
+ o.disabledAt ? "Enable" : "Disable"
1397
+ )
1398
+ ],
1399
+ { textAlign: "right", whiteSpace: "nowrap" }
1400
+ )
1401
+ ]);
1402
+ })
1403
+ ) : null,
1404
+ r && r.links.length > 0 ? [
1405
+ h10("h2", { style: styles.subtitle }, "Pending links"),
1406
+ h10(
1407
+ "ul",
1408
+ { style: styles.list },
1409
+ r.links.map(
1410
+ (l) => h10(
1411
+ "li",
1412
+ {
1413
+ key: l.id,
1414
+ style: { ...styles.row, display: "flex", gap: "12px", alignItems: "center" }
1415
+ },
1416
+ [
1417
+ pill(l.kind === "invite" ? "info" : "warn", l.kind, false),
1418
+ h10("span", { style: { flex: 1 } }, [
1419
+ l.email,
1420
+ l.kind === "invite" ? h10("span", { style: styles.muted }, ` \xB7 ${levelOf(l.scopes)}`) : null
1421
+ ]),
1422
+ h10(
1423
+ "span",
1424
+ { style: styles.muted },
1425
+ `expires ${new Date(l.expiresAt).toLocaleString()}`
1426
+ ),
1427
+ h10(
1428
+ "button",
1429
+ {
1430
+ type: "button",
1431
+ style: { ...styles.buttonDanger, ...small },
1432
+ onClick: () => void revokeLink(l.id).catch(() => {
1433
+ })
1434
+ },
1435
+ "Revoke"
1436
+ )
1437
+ ]
1438
+ )
1439
+ )
1440
+ )
1441
+ ] : null
1442
+ ]);
1443
+ };
1444
+ }
1445
+ });
1446
+
1170
1447
  // src/screens/TokensScreen.ts
1171
1448
  import { useAdminTokens } from "@fonderie/vue-admin";
1172
- import { defineComponent as defineComponent8, h as h10, ref as ref2 } from "vue";
1449
+ import { defineComponent as defineComponent9, h as h11, ref as ref3 } from "vue";
1173
1450
  var ALL = ["read", "write", "secrets"];
1174
- var TokensScreen = defineComponent8({
1451
+ var TokensScreen = defineComponent9({
1175
1452
  name: "FonderieTokensScreen",
1176
1453
  props: { client: { type: Object, required: true } },
1177
1454
  setup(props) {
1178
1455
  const { report, isLoading, error, issue, revoke } = useAdminTokens(props.client);
1179
- const name = ref2("");
1180
- const scopes = ref2(["read"]);
1181
- const days = ref2("");
1182
- const minted = ref2(null);
1456
+ const name = ref3("");
1457
+ const scopes = ref3(["read"]);
1458
+ const days = ref3("");
1459
+ const minted = ref3(null);
1183
1460
  const toggle = (s) => scopes.value = scopes.value.includes(s) ? scopes.value.filter((x) => x !== s) : [...scopes.value, s];
1184
1461
  return () => {
1185
1462
  const r = report.value;
1186
- return h10("div", { style: styles.container }, [
1463
+ return h11("div", { style: styles.container }, [
1187
1464
  pageHeader(
1188
- "Access",
1189
- "Who can open this surface: the root token's strength, scoped tokens you have issued, and any legacy per-module tokens."
1465
+ "Tokens",
1466
+ "Credentials for machines: the root token's strength, scoped tokens for the CLI and CI, and any legacy per-module tokens. People sign in as operators."
1190
1467
  ),
1191
- error.value ? h10(
1468
+ error.value ? h11(
1192
1469
  "p",
1193
1470
  { style: styles.error, role: "alert" },
1194
1471
  error.value.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.value.explanation
1195
1472
  ) : null,
1196
- h10("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Root token"),
1197
- r ? h10(
1473
+ h11("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Root token"),
1474
+ r ? h11(
1198
1475
  "div",
1199
1476
  { style: { ...styles.card, display: "flex", gap: "12px", alignItems: "flex-start" } },
1200
1477
  [
1201
1478
  icon("lock", 18, { marginTop: "2px" }),
1202
- h10("div", [
1479
+ h11("div", [
1203
1480
  pill(r.admin.ok ? "ok" : "bad", r.admin.ok ? "strong" : "weak"),
1204
1481
  ...r.admin.problems.map(
1205
- (p) => h10(
1482
+ (p) => h11(
1206
1483
  "div",
1207
1484
  {
1208
1485
  key: p.message,
@@ -1218,13 +1495,13 @@ var TokensScreen = defineComponent8({
1218
1495
  ])
1219
1496
  ]
1220
1497
  ) : null,
1221
- h10("h2", { style: styles.subtitle }, "Issued tokens"),
1222
- r?.issued === null ? h10(
1498
+ h11("h2", { style: styles.subtitle }, "Issued tokens"),
1499
+ r?.issued === null ? h11(
1223
1500
  "p",
1224
1501
  { style: styles.muted },
1225
1502
  "Issuing is off \u2014 give AdminModule a store and run its migrations."
1226
1503
  ) : [
1227
- minted.value ? h10(
1504
+ minted.value ? h11(
1228
1505
  "div",
1229
1506
  {
1230
1507
  style: {
@@ -1234,9 +1511,9 @@ var TokensScreen = defineComponent8({
1234
1511
  }
1235
1512
  },
1236
1513
  [
1237
- h10("strong", "Copy this now \u2014 it is never shown again."),
1514
+ h11("strong", "Copy this now \u2014 it is never shown again."),
1238
1515
  ` (${minted.value.name})`,
1239
- h10(
1516
+ h11(
1240
1517
  "div",
1241
1518
  {
1242
1519
  style: {
@@ -1251,7 +1528,7 @@ var TokensScreen = defineComponent8({
1251
1528
  )
1252
1529
  ]
1253
1530
  ) : null,
1254
- h10(
1531
+ h11(
1255
1532
  "form",
1256
1533
  {
1257
1534
  style: styles.toolbar,
@@ -1271,7 +1548,7 @@ var TokensScreen = defineComponent8({
1271
1548
  }
1272
1549
  },
1273
1550
  [
1274
- h10("input", {
1551
+ h11("input", {
1275
1552
  value: name.value,
1276
1553
  onInput: (e) => name.value = e.target.value,
1277
1554
  placeholder: "name",
@@ -1279,7 +1556,7 @@ var TokensScreen = defineComponent8({
1279
1556
  "aria-label": "Name"
1280
1557
  }),
1281
1558
  ...ALL.map(
1282
- (s) => h10(
1559
+ (s) => h11(
1283
1560
  "label",
1284
1561
  {
1285
1562
  key: s,
@@ -1293,7 +1570,7 @@ var TokensScreen = defineComponent8({
1293
1570
  }
1294
1571
  },
1295
1572
  [
1296
- h10("input", {
1573
+ h11("input", {
1297
1574
  type: "checkbox",
1298
1575
  checked: scopes.value.includes(s),
1299
1576
  onChange: () => toggle(s)
@@ -1302,14 +1579,14 @@ var TokensScreen = defineComponent8({
1302
1579
  ]
1303
1580
  )
1304
1581
  ),
1305
- h10("input", {
1582
+ h11("input", {
1306
1583
  value: days.value,
1307
1584
  onInput: (e) => days.value = e.target.value,
1308
1585
  placeholder: "days (optional)",
1309
1586
  style: { ...styles.input, width: "140px" },
1310
1587
  "aria-label": "Days"
1311
1588
  }),
1312
- h10(
1589
+ h11(
1313
1590
  "button",
1314
1591
  {
1315
1592
  type: "submit",
@@ -1323,7 +1600,7 @@ var TokensScreen = defineComponent8({
1323
1600
  r?.issued && r.issued.length > 0 ? table(
1324
1601
  ["Name", "Scopes", "Created", "Expires", "Last used", ""],
1325
1602
  r.issued.map(
1326
- (t2) => h10("tr", { key: t2.id }, [
1603
+ (t2) => h11("tr", { key: t2.id }, [
1327
1604
  td([t2.name, t2.revokedAt ? [" ", pill("neutral", "revoked")] : ""]),
1328
1605
  td(t2.scopes.join(", "), styles.mono),
1329
1606
  td(
@@ -1339,7 +1616,7 @@ var TokensScreen = defineComponent8({
1339
1616
  styles.muted
1340
1617
  ),
1341
1618
  td(
1342
- t2.revokedAt ? "" : h10(
1619
+ t2.revokedAt ? "" : h11(
1343
1620
  "button",
1344
1621
  {
1345
1622
  type: "button",
@@ -1363,19 +1640,19 @@ var TokensScreen = defineComponent8({
1363
1640
  "tokens"
1364
1641
  )
1365
1642
  ],
1366
- h10("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
1367
- r && r.legacy.length === 0 ? h10(
1643
+ h11("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
1644
+ r && r.legacy.length === 0 ? h11(
1368
1645
  "p",
1369
1646
  { style: styles.muted },
1370
1647
  "None \u2014 every brick's admin surface goes through this token."
1371
- ) : h10(
1648
+ ) : h11(
1372
1649
  "ul",
1373
1650
  { style: styles.list },
1374
1651
  (r?.legacy ?? []).map(
1375
- (l) => h10("li", { key: l.module, style: styles.row }, [
1376
- h10("span", { style: styles.mono }, l.module),
1652
+ (l) => h11("li", { key: l.module, style: styles.row }, [
1653
+ h11("span", { style: styles.mono }, l.module),
1377
1654
  " ",
1378
- h10(
1655
+ h11(
1379
1656
  "span",
1380
1657
  { style: styles.muted },
1381
1658
  "still registers its standalone routes with its own token (deprecated)"
@@ -1396,17 +1673,17 @@ import {
1396
1673
  useAdminUserSessions,
1397
1674
  useAdminUsers
1398
1675
  } from "@fonderie/vue-admin";
1399
- import { computed, defineComponent as defineComponent9, h as h11, ref as ref3 } from "vue";
1400
- var UsersScreen = defineComponent9({
1676
+ import { computed, defineComponent as defineComponent10, h as h12, ref as ref4 } from "vue";
1677
+ var UsersScreen = defineComponent10({
1401
1678
  name: "FonderieUsersScreen",
1402
1679
  props: {
1403
1680
  client: { type: Object, required: true },
1404
1681
  pageSize: { type: Number, default: 50 }
1405
1682
  },
1406
1683
  setup(props) {
1407
- const input = ref3("");
1408
- const email = ref3("");
1409
- const selectedId = ref3("");
1684
+ const input = ref4("");
1685
+ const email = ref4("");
1686
+ const selectedId = ref4("");
1410
1687
  const list = useAdminUsers(props.client, { limit: props.pageSize });
1411
1688
  const { user, isLoading, error, suspend, unsuspend, revokeSessions } = useAdminUser(
1412
1689
  props.client,
@@ -1417,7 +1694,7 @@ var UsersScreen = defineComponent9({
1417
1694
  const history = useAdminLoginHistory(props.client, userId, { limit: 20 });
1418
1695
  const showList = computed(() => !email.value && !selectedId.value);
1419
1696
  const yesNo = (v) => v ? pill("ok", "yes") : pill("neutral", "no");
1420
- const row = (k, v) => h11("tr", [td(k), td(v)]);
1697
+ const row = (k, v) => h12("tr", [td(k), td(v)]);
1421
1698
  const clear = () => {
1422
1699
  email.value = "";
1423
1700
  selectedId.value = "";
@@ -1425,12 +1702,12 @@ var UsersScreen = defineComponent9({
1425
1702
  };
1426
1703
  return () => {
1427
1704
  const u = user.value;
1428
- return h11("div", { style: styles.container }, [
1705
+ return h12("div", { style: styles.container }, [
1429
1706
  pageHeader(
1430
1707
  "Users",
1431
1708
  "Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
1432
1709
  ),
1433
- h11(
1710
+ h12(
1434
1711
  "form",
1435
1712
  {
1436
1713
  style: styles.toolbar,
@@ -1441,7 +1718,7 @@ var UsersScreen = defineComponent9({
1441
1718
  }
1442
1719
  },
1443
1720
  [
1444
- h11("input", {
1721
+ h12("input", {
1445
1722
  type: "email",
1446
1723
  value: input.value,
1447
1724
  onInput: (e) => input.value = e.target.value,
@@ -1449,7 +1726,7 @@ var UsersScreen = defineComponent9({
1449
1726
  style: { ...styles.input, minWidth: "280px" },
1450
1727
  "aria-label": "Email"
1451
1728
  }),
1452
- h11(
1729
+ h12(
1453
1730
  "button",
1454
1731
  { type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
1455
1732
  [icon("search", 14), "Look up"]
@@ -1457,14 +1734,14 @@ var UsersScreen = defineComponent9({
1457
1734
  showList.value ? refreshButton("Refresh", list.isLoading.value, () => void list.refresh()) : actionButton([icon("back", 14), "All users"], false, clear, styles.buttonGhost)
1458
1735
  ]
1459
1736
  ),
1460
- showList.value ? h11("div", [
1461
- list.error.value ? h11("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
1737
+ showList.value ? h12("div", [
1738
+ list.error.value ? h12("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
1462
1739
  list.users.value.length === 0 && !list.isLoading.value ? empty("No users yet", "Sign-ups appear here as they happen.", "users") : table(
1463
1740
  ["Email", "Name", "Created", "Status"],
1464
1741
  list.users.value.map(
1465
- (u2) => h11("tr", { key: u2.id }, [
1742
+ (u2) => h12("tr", { key: u2.id }, [
1466
1743
  td(
1467
- h11(
1744
+ h12(
1468
1745
  "button",
1469
1746
  {
1470
1747
  type: "button",
@@ -1475,7 +1752,7 @@ var UsersScreen = defineComponent9({
1475
1752
  )
1476
1753
  ),
1477
1754
  td(
1478
- `${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || h11("span", { style: styles.muted }, "\u2014")
1755
+ `${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || h12("span", { style: styles.muted }, "\u2014")
1479
1756
  ),
1480
1757
  td(new Date(u2.createdAt).toLocaleDateString()),
1481
1758
  td(
@@ -1484,24 +1761,24 @@ var UsersScreen = defineComponent9({
1484
1761
  ])
1485
1762
  )
1486
1763
  ),
1487
- list.isLoading.value ? h11("p", { style: styles.status }, "Loading\u2026") : null,
1764
+ list.isLoading.value ? h12("p", { style: styles.status }, "Loading\u2026") : null,
1488
1765
  list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
1489
1766
  ]) : null,
1490
- error.value ? h11(
1767
+ error.value ? h12(
1491
1768
  "p",
1492
1769
  { style: styles.error, role: "alert" },
1493
1770
  error.value.status === 404 ? "No user with that email." : error.value.explanation
1494
1771
  ) : null,
1495
1772
  u ? [
1496
- h11("h2", { style: styles.subtitle }, [
1773
+ h12("h2", { style: styles.subtitle }, [
1497
1774
  u.firstName || u.lastName ? `${u.firstName} ${u.lastName}`.trim() : u.email,
1498
1775
  " ",
1499
1776
  u.suspended ? pill("warn", "suspended") : null,
1500
1777
  u.deletedAt ? pill("neutral", "deleted") : null
1501
1778
  ]),
1502
- h11("table", { style: styles.table }, [
1503
- h11("tbody", [
1504
- row("id", h11("span", { style: styles.mono }, u.id)),
1779
+ h12("table", { style: styles.table }, [
1780
+ h12("tbody", [
1781
+ row("id", h12("span", { style: styles.mono }, u.id)),
1505
1782
  row("email", [u.email, " \xB7 verified ", yesNo(u.isEmailVerified)]),
1506
1783
  row("MFA", yesNo(u.mfaEnabled)),
1507
1784
  row(
@@ -1510,12 +1787,12 @@ var UsersScreen = defineComponent9({
1510
1787
  ),
1511
1788
  row(
1512
1789
  "last login",
1513
- u.lastLogin ? new Date(u.lastLogin).toLocaleString() : h11("span", { style: styles.muted }, "never")
1790
+ u.lastLogin ? new Date(u.lastLogin).toLocaleString() : h12("span", { style: styles.muted }, "never")
1514
1791
  ),
1515
1792
  row("created", new Date(u.createdAt).toLocaleString())
1516
1793
  ])
1517
1794
  ]),
1518
- h11("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
1795
+ h12("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
1519
1796
  u.suspended ? actionButton("Unsuspend", isLoading.value, () => void unsuspend()) : actionButton(
1520
1797
  "Suspend",
1521
1798
  isLoading.value,
@@ -1528,19 +1805,19 @@ var UsersScreen = defineComponent9({
1528
1805
  () => void revokeSessions().then(() => sessions.refresh())
1529
1806
  )
1530
1807
  ]),
1531
- h11("h2", { style: styles.subtitle }, "Live sessions"),
1532
- sessions.sessions.value.length === 0 ? h11("p", { style: styles.muted }, "None.") : h11(
1808
+ h12("h2", { style: styles.subtitle }, "Live sessions"),
1809
+ sessions.sessions.value.length === 0 ? h12("p", { style: styles.muted }, "None.") : h12(
1533
1810
  "ul",
1534
1811
  { style: styles.list },
1535
1812
  sessions.sessions.value.map(
1536
- (s) => h11("li", { key: s.id, style: styles.row }, [
1537
- h11("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
1813
+ (s) => h12("li", { key: s.id, style: styles.row }, [
1814
+ h12("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
1538
1815
  " ",
1539
- h11("span", { style: styles.muted }, describeLocation(s.location) ?? ""),
1816
+ h12("span", { style: styles.muted }, describeLocation(s.location) ?? ""),
1540
1817
  " ",
1541
- h11("span", { style: styles.muted }, s.userAgent ?? ""),
1818
+ h12("span", { style: styles.muted }, s.userAgent ?? ""),
1542
1819
  " ",
1543
- h11(
1820
+ h12(
1544
1821
  "span",
1545
1822
  { style: styles.muted },
1546
1823
  `since ${new Date(s.createdAt).toLocaleString()}`
@@ -1548,22 +1825,22 @@ var UsersScreen = defineComponent9({
1548
1825
  ])
1549
1826
  )
1550
1827
  ),
1551
- h11("h2", { style: styles.subtitle }, "Recent sign-ins"),
1552
- history.events.value.length === 0 && !history.isLoading.value ? h11("p", { style: styles.muted }, "None recorded.") : h11(
1828
+ h12("h2", { style: styles.subtitle }, "Recent sign-ins"),
1829
+ history.events.value.length === 0 && !history.isLoading.value ? h12("p", { style: styles.muted }, "None recorded.") : h12(
1553
1830
  "ul",
1554
1831
  { style: styles.list },
1555
1832
  history.events.value.map(
1556
- (e) => h11("li", { key: e.id, style: styles.row }, [
1833
+ (e) => h12("li", { key: e.id, style: styles.row }, [
1557
1834
  pill(e.outcome === "success" ? "ok" : "bad", e.outcome),
1558
1835
  " ",
1559
- h11("span", { style: styles.muted }, e.method),
1836
+ h12("span", { style: styles.muted }, e.method),
1560
1837
  " ",
1561
- h11("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
1838
+ h12("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
1562
1839
  " ",
1563
- h11("span", { style: styles.muted }, describeLocation(e.location) ?? ""),
1840
+ h12("span", { style: styles.muted }, describeLocation(e.location) ?? ""),
1564
1841
  e.location?.proxy || e.location?.hosting ? [" ", pill("warn", e.location.proxy ? "proxy/VPN" : "hosting")] : "",
1565
1842
  " ",
1566
- h11("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
1843
+ h12("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
1567
1844
  ])
1568
1845
  )
1569
1846
  ),
@@ -1576,8 +1853,8 @@ var UsersScreen = defineComponent9({
1576
1853
 
1577
1854
  // src/screens/CatalogScreen.ts
1578
1855
  import { useAdminCatalog } from "@fonderie/vue-admin";
1579
- import { defineComponent as defineComponent10, h as h12 } from "vue";
1580
- var CatalogScreen = defineComponent10({
1856
+ import { defineComponent as defineComponent11, h as h13 } from "vue";
1857
+ var CatalogScreen = defineComponent11({
1581
1858
  name: "FonderieCatalogScreen",
1582
1859
  props: { client: { type: Object, required: true } },
1583
1860
  setup(props) {
@@ -1585,17 +1862,17 @@ var CatalogScreen = defineComponent10({
1585
1862
  const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
1586
1863
  return () => {
1587
1864
  const c = catalog.value;
1588
- return h12("div", { style: styles.container }, [
1865
+ return h13("div", { style: styles.container }, [
1589
1866
  pageHeader(
1590
1867
  "Catalog",
1591
1868
  "What you sell: plans as configured in code, and as stored in the database, side by side.",
1592
1869
  [refreshButton("Refresh", isLoading.value, () => void refresh())]
1593
1870
  ),
1594
- error.value ? h12("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
1595
- isLoading.value && !c ? h12("p", { style: styles.status }, "Loading\u2026") : null,
1871
+ error.value ? h13("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
1872
+ isLoading.value && !c ? h13("p", { style: styles.status }, "Loading\u2026") : null,
1596
1873
  c ? [
1597
- h12("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Configured (code)"),
1598
- h12(
1874
+ h13("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Configured (code)"),
1875
+ h13(
1599
1876
  "pre",
1600
1877
  {
1601
1878
  style: {
@@ -1609,7 +1886,7 @@ var CatalogScreen = defineComponent10({
1609
1886
  },
1610
1887
  JSON.stringify(c.configured, null, 2)
1611
1888
  ),
1612
- h12("h2", { style: styles.subtitle }, "Stored (database)"),
1889
+ h13("h2", { style: styles.subtitle }, "Stored (database)"),
1613
1890
  c.stored.length === 0 ? empty(
1614
1891
  "No stored plans",
1615
1892
  "Plans are configured in code; nothing has been written to the database.",
@@ -1617,10 +1894,10 @@ var CatalogScreen = defineComponent10({
1617
1894
  ) : table(
1618
1895
  ["Plan", "Tier", "Seats", "Monthly", "Yearly", "Trial", ""],
1619
1896
  c.stored.map(
1620
- (p) => h12("tr", { key: p.id }, [
1897
+ (p) => h13("tr", { key: p.id }, [
1621
1898
  td([
1622
- h12("strong", p.name),
1623
- p.description ? h12("div", { style: styles.muted }, p.description) : ""
1899
+ h13("strong", p.name),
1900
+ p.description ? h13("div", { style: styles.muted }, p.description) : ""
1624
1901
  ]),
1625
1902
  td(String(p.tier)),
1626
1903
  td(p.seats == null ? "\u221E" : String(p.seats)),
@@ -1628,7 +1905,7 @@ var CatalogScreen = defineComponent10({
1628
1905
  td(money(p.pricing?.yearly)),
1629
1906
  td(p.trialDays ? `${p.trialDays} d` : "\u2014"),
1630
1907
  td(
1631
- h12(
1908
+ h13(
1632
1909
  "button",
1633
1910
  {
1634
1911
  type: "button",
@@ -1653,17 +1930,17 @@ var CatalogScreen = defineComponent10({
1653
1930
 
1654
1931
  // src/screens/SubscriberScreen.ts
1655
1932
  import { useAdminSubscriber, useAdminSubscribers } from "@fonderie/vue-admin";
1656
- import { defineComponent as defineComponent11, h as h13, ref as ref4 } from "vue";
1657
- var SubscriberScreen = defineComponent11({
1933
+ import { defineComponent as defineComponent12, h as h14, ref as ref5 } from "vue";
1934
+ var SubscriberScreen = defineComponent12({
1658
1935
  name: "FonderieSubscriberScreen",
1659
1936
  props: {
1660
1937
  client: { type: Object, required: true },
1661
1938
  pageSize: { type: Number, default: 50 }
1662
1939
  },
1663
1940
  setup(props) {
1664
- const type = ref4("user");
1665
- const idInput = ref4("");
1666
- const subscriber = ref4(null);
1941
+ const type = ref5("user");
1942
+ const idInput = ref5("");
1943
+ const subscriber = ref5(null);
1667
1944
  const list = useAdminSubscribers(props.client, { limit: props.pageSize });
1668
1945
  const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = useAdminSubscriber(props.client, subscriber, { limit: 20 });
1669
1946
  const open = (t2, id) => {
@@ -1672,10 +1949,10 @@ var SubscriberScreen = defineComponent11({
1672
1949
  idInput.value = id;
1673
1950
  subscriber.value = { type: t2, id };
1674
1951
  };
1675
- const amount = ref4("");
1676
- const note = ref4("");
1677
- const granted = ref4(null);
1678
- const input = (model, placeholder, extra = {}) => h13("input", {
1952
+ const amount = ref5("");
1953
+ const note = ref5("");
1954
+ const granted = ref5(null);
1955
+ const input = (model, placeholder, extra = {}) => h14("input", {
1679
1956
  value: model.value,
1680
1957
  onInput: (e) => model.value = e.target.value,
1681
1958
  placeholder,
@@ -1685,12 +1962,12 @@ var SubscriberScreen = defineComponent11({
1685
1962
  return () => {
1686
1963
  const s = subscription.value;
1687
1964
  const w = wallet.value;
1688
- return h13("div", { style: styles.container }, [
1965
+ return h14("div", { style: styles.container }, [
1689
1966
  pageHeader(
1690
1967
  "Subscribers",
1691
1968
  "Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
1692
1969
  ),
1693
- h13(
1970
+ h14(
1694
1971
  "form",
1695
1972
  {
1696
1973
  style: styles.toolbar,
@@ -1701,7 +1978,7 @@ var SubscriberScreen = defineComponent11({
1701
1978
  }
1702
1979
  },
1703
1980
  [
1704
- h13(
1981
+ h14(
1705
1982
  "select",
1706
1983
  {
1707
1984
  value: type.value,
@@ -1710,17 +1987,17 @@ var SubscriberScreen = defineComponent11({
1710
1987
  "aria-label": "Type"
1711
1988
  },
1712
1989
  [
1713
- h13("option", { value: "user" }, "user"),
1714
- h13("option", { value: "workspace" }, "workspace")
1990
+ h14("option", { value: "user" }, "user"),
1991
+ h14("option", { value: "workspace" }, "workspace")
1715
1992
  ]
1716
1993
  ),
1717
1994
  input(idInput, "subscriber id", { minWidth: "280px" }),
1718
- h13(
1995
+ h14(
1719
1996
  "button",
1720
1997
  { type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
1721
1998
  [icon("search", 14), "Look up"]
1722
1999
  ),
1723
- subscriber.value ? h13(
2000
+ subscriber.value ? h14(
1724
2001
  "button",
1725
2002
  {
1726
2003
  type: "button",
@@ -1732,7 +2009,7 @@ var SubscriberScreen = defineComponent11({
1732
2009
  }
1733
2010
  },
1734
2011
  [icon("back", 14), "All subscribers"]
1735
- ) : h13(
2012
+ ) : h14(
1736
2013
  "button",
1737
2014
  {
1738
2015
  type: "button",
@@ -1744,8 +2021,8 @@ var SubscriberScreen = defineComponent11({
1744
2021
  )
1745
2022
  ]
1746
2023
  ),
1747
- !subscriber.value ? h13("div", [
1748
- list.error.value ? h13("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
2024
+ !subscriber.value ? h14("div", [
2025
+ list.error.value ? h14("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
1749
2026
  list.subscriptions.value.length === 0 && !list.isLoading.value ? empty(
1750
2027
  "No subscribers yet",
1751
2028
  "Subscriptions appear here once someone checks out.",
@@ -1753,9 +2030,9 @@ var SubscriberScreen = defineComponent11({
1753
2030
  ) : table(
1754
2031
  ["Subscriber", "Plan", "Status", "Renews"],
1755
2032
  list.subscriptions.value.map(
1756
- (sub) => h13("tr", { key: sub.id }, [
2033
+ (sub) => h14("tr", { key: sub.id }, [
1757
2034
  td(
1758
- h13(
2035
+ h14(
1759
2036
  "button",
1760
2037
  {
1761
2038
  type: "button",
@@ -1774,13 +2051,13 @@ var SubscriberScreen = defineComponent11({
1774
2051
  sub.cancelAtPeriodEnd ? [" ", pill("warn", "cancels", false)] : null
1775
2052
  ]),
1776
2053
  td(
1777
- sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : h13("span", { style: styles.muted }, "\u2014")
2054
+ sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : h14("span", { style: styles.muted }, "\u2014")
1778
2055
  )
1779
2056
  ])
1780
2057
  )
1781
2058
  ),
1782
- list.isLoading.value ? h13("p", { style: styles.status }, "Loading\u2026") : null,
1783
- list.hasMore.value && !list.isLoading.value ? h13(
2059
+ list.isLoading.value ? h14("p", { style: styles.status }, "Loading\u2026") : null,
2060
+ list.hasMore.value && !list.isLoading.value ? h14(
1784
2061
  "button",
1785
2062
  {
1786
2063
  type: "button",
@@ -1790,28 +2067,28 @@ var SubscriberScreen = defineComponent11({
1790
2067
  "Load more"
1791
2068
  ) : null
1792
2069
  ]) : null,
1793
- error.value ? h13("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
2070
+ error.value ? h14("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
1794
2071
  subscriber.value && !isLoading.value ? [
1795
- h13("h2", { style: styles.subtitle }, "Subscription"),
1796
- s ? h13("table", { style: styles.table }, [
1797
- h13("tbody", [
1798
- h13("tr", [td("plan"), td([h13("strong", s.plan), ` \xB7 ${s.interval}`])]),
1799
- h13("tr", [
2072
+ h14("h2", { style: styles.subtitle }, "Subscription"),
2073
+ s ? h14("table", { style: styles.table }, [
2074
+ h14("tbody", [
2075
+ h14("tr", [td("plan"), td([h14("strong", s.plan), ` \xB7 ${s.interval}`])]),
2076
+ h14("tr", [
1800
2077
  td("status"),
1801
2078
  td(`${s.status}${s.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""}`)
1802
2079
  ]),
1803
- h13("tr", [
2080
+ h14("tr", [
1804
2081
  td("period"),
1805
2082
  td(
1806
2083
  `${s.currentPeriodStart ? new Date(s.currentPeriodStart).toLocaleDateString() : "\u2014"} \u2192 ${s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : "\u2014"}`
1807
2084
  )
1808
2085
  ]),
1809
- h13("tr", [td("provider"), td(s.providerSubscriptionId ?? "\u2014", styles.mono)])
2086
+ h14("tr", [td("provider"), td(s.providerSubscriptionId ?? "\u2014", styles.mono)])
1810
2087
  ])
1811
- ]) : h13("p", { style: styles.muted }, "No subscription."),
1812
- h13("h2", { style: styles.subtitle }, "Wallet"),
2088
+ ]) : h14("p", { style: styles.muted }, "No subscription."),
2089
+ h14("h2", { style: styles.subtitle }, "Wallet"),
1813
2090
  w ? [
1814
- h13(
2091
+ h14(
1815
2092
  "p",
1816
2093
  {
1817
2094
  style: {
@@ -1823,7 +2100,7 @@ var SubscriberScreen = defineComponent11({
1823
2100
  }
1824
2101
  },
1825
2102
  [
1826
- h13(
2103
+ h14(
1827
2104
  "strong",
1828
2105
  {
1829
2106
  style: {
@@ -1834,14 +2111,14 @@ var SubscriberScreen = defineComponent11({
1834
2111
  w.balance
1835
2112
  ),
1836
2113
  ` ${w.currency} `,
1837
- h13(
2114
+ h14(
1838
2115
  "span",
1839
2116
  { style: styles.muted },
1840
2117
  `(granted ${w.granted ?? "0"} \xB7 purchased ${w.purchased ?? "0"}) \xB7 minor units, precision ${w.precision}`
1841
2118
  )
1842
2119
  ]
1843
2120
  ),
1844
- h13(
2121
+ h14(
1845
2122
  "form",
1846
2123
  {
1847
2124
  style: styles.toolbar,
@@ -1866,7 +2143,7 @@ var SubscriberScreen = defineComponent11({
1866
2143
  [
1867
2144
  input(amount, "amount (minor units)"),
1868
2145
  input(note, "reason (optional)", { minWidth: "220px" }),
1869
- h13(
2146
+ h14(
1870
2147
  "button",
1871
2148
  {
1872
2149
  type: "submit",
@@ -1875,14 +2152,14 @@ var SubscriberScreen = defineComponent11({
1875
2152
  },
1876
2153
  [icon("plus", 14), "Grant"]
1877
2154
  ),
1878
- granted.value ? h13("span", { style: styles.ok }, granted.value) : null
2155
+ granted.value ? h14("span", { style: styles.ok }, granted.value) : null
1879
2156
  ]
1880
2157
  ),
1881
- h13("h2", { style: styles.subtitle }, "Ledger"),
2158
+ h14("h2", { style: styles.subtitle }, "Ledger"),
1882
2159
  ledger.value.length === 0 ? empty("Nothing moved yet", void 0, "subscriber") : table(
1883
2160
  ["When", "Kind", "Amount", "Note"],
1884
2161
  ledger.value.map(
1885
- (t2) => h13("tr", { key: t2.id }, [
2162
+ (t2) => h14("tr", { key: t2.id }, [
1886
2163
  td(new Date(t2.createdAt).toLocaleString(), styles.muted),
1887
2164
  td(t2.type),
1888
2165
  td(t2.amount, styles.mono),
@@ -1890,7 +2167,7 @@ var SubscriberScreen = defineComponent11({
1890
2167
  ])
1891
2168
  )
1892
2169
  ),
1893
- hasMoreLedger.value ? h13(
2170
+ hasMoreLedger.value ? h14(
1894
2171
  "button",
1895
2172
  {
1896
2173
  type: "button",
@@ -1899,7 +2176,7 @@ var SubscriberScreen = defineComponent11({
1899
2176
  },
1900
2177
  "Load more"
1901
2178
  ) : null
1902
- ] : h13(
2179
+ ] : h14(
1903
2180
  "p",
1904
2181
  { style: styles.muted },
1905
2182
  "No wallet \u2014 billing has no wallet configuration."
@@ -1912,33 +2189,33 @@ var SubscriberScreen = defineComponent11({
1912
2189
 
1913
2190
  // src/screens/AuditScreen.ts
1914
2191
  import { useAdminAudit } from "@fonderie/vue-admin";
1915
- import { defineComponent as defineComponent12, h as h14, ref as ref5 } from "vue";
1916
- var AuditScreen = defineComponent12({
2192
+ import { defineComponent as defineComponent13, h as h15, ref as ref6 } from "vue";
2193
+ var AuditScreen = defineComponent13({
1917
2194
  name: "FonderieAuditScreen",
1918
2195
  props: {
1919
2196
  client: { type: Object, required: true },
1920
2197
  pageSize: { type: Number, default: 50 }
1921
2198
  },
1922
2199
  setup(props) {
1923
- const draft = ref5({ workspaceId: "", type: "", actorId: "" });
1924
- const query = ref5({ limit: props.pageSize });
2200
+ const draft = ref6({ workspaceId: "", type: "", actorId: "" });
2201
+ const query = ref6({ limit: props.pageSize });
1925
2202
  const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(
1926
2203
  props.client,
1927
2204
  query
1928
2205
  );
1929
- const field = (key, placeholder) => h14("input", {
2206
+ const field = (key, placeholder) => h15("input", {
1930
2207
  value: draft.value[key],
1931
2208
  onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
1932
2209
  placeholder,
1933
2210
  style: styles.input,
1934
2211
  "aria-label": placeholder
1935
2212
  });
1936
- return () => h14("div", { style: styles.container }, [
2213
+ return () => h15("div", { style: styles.container }, [
1937
2214
  pageHeader(
1938
2215
  "Audit",
1939
2216
  "What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
1940
2217
  ),
1941
- h14(
2218
+ h15(
1942
2219
  "form",
1943
2220
  {
1944
2221
  style: styles.toolbar,
@@ -1957,7 +2234,7 @@ var AuditScreen = defineComponent12({
1957
2234
  field("workspaceId", "workspace id (all if empty)"),
1958
2235
  field("type", "event type"),
1959
2236
  field("actorId", "actor id"),
1960
- h14(
2237
+ h15(
1961
2238
  "button",
1962
2239
  { type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
1963
2240
  [icon("search", 14), "Filter"]
@@ -1965,11 +2242,11 @@ var AuditScreen = defineComponent12({
1965
2242
  refreshButton("Refresh", isLoading.value, () => void refresh())
1966
2243
  ]
1967
2244
  ),
1968
- error.value ? h14("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
2245
+ error.value ? h15("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
1969
2246
  events.value.length === 0 && !isLoading.value ? empty("Nothing recorded for this filter", void 0, "audit") : table(
1970
2247
  ["When", "Type", "Workspace", "Actor", "Request"],
1971
2248
  events.value.map(
1972
- (e) => h14("tr", { key: e.id }, [
2249
+ (e) => h15("tr", { key: e.id }, [
1973
2250
  td(new Date(e.createdAt).toLocaleString(), styles.muted),
1974
2251
  td(e.type, styles.mono),
1975
2252
  td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
@@ -1978,7 +2255,7 @@ var AuditScreen = defineComponent12({
1978
2255
  ])
1979
2256
  )
1980
2257
  ),
1981
- isLoading.value ? h14("p", { style: styles.status }, "Loading\u2026") : null,
2258
+ isLoading.value ? h15("p", { style: styles.status }, "Loading\u2026") : null,
1982
2259
  hasMore.value && !isLoading.value ? loadMoreButton(() => void loadMore()) : null
1983
2260
  ]);
1984
2261
  }
@@ -2011,7 +2288,8 @@ var NAV = [
2011
2288
  items: [
2012
2289
  { page: "audit", label: "Audit", needs: "audit" },
2013
2290
  { page: "log", label: "Admin log" },
2014
- { page: "tokens", label: "Access" },
2291
+ { page: "operators", label: "Operators", needs: "operators" },
2292
+ { page: "tokens", label: "Tokens" },
2015
2293
  { page: "migrations", label: "Migrations" }
2016
2294
  ]
2017
2295
  }
@@ -2030,10 +2308,11 @@ var ICON = {
2030
2308
  audit: "audit",
2031
2309
  log: "log",
2032
2310
  tokens: "tokens",
2311
+ operators: "users",
2033
2312
  migrations: "migrations"
2034
2313
  };
2035
2314
  var NARROW = "(max-width: 820px)";
2036
- var AdminShell = defineComponent13({
2315
+ var AdminShell = defineComponent14({
2037
2316
  name: "FonderieAdminShell",
2038
2317
  props: {
2039
2318
  client: { type: Object, required: true },
@@ -2055,62 +2334,72 @@ var AdminShell = defineComponent13({
2055
2334
  appName: { type: String, default: "Admin" },
2056
2335
  // A badge beside the name — the deployment's environment ("production").
2057
2336
  // Production is tinted so an operator always knows where their clicks land.
2058
- envLabel: { type: String, default: void 0 }
2337
+ envLabel: { type: String, default: void 0 },
2338
+ // The deployment has operator accounts (@fonderie/admin with a store):
2339
+ // shows the Operators page.
2340
+ operators: { type: Boolean, default: false },
2341
+ // The signed-in operator's email, when a person (not a token) is signed in.
2342
+ currentOperator: { type: String, default: void 0 }
2059
2343
  },
2060
2344
  // `footer` slot: pinned to the bottom of the sidebar — session controls
2061
2345
  // (theme, sign out). The React shell takes the same thing as a `footer` prop.
2062
2346
  emits: { navigate: (_page) => true },
2063
2347
  setup(props, { emit, slots }) {
2064
2348
  const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
2065
- const narrow = ref6(query?.matches ?? false);
2066
- const drawer = ref6(false);
2349
+ const narrow = ref7(query?.matches ?? false);
2350
+ const drawer = ref7(false);
2067
2351
  const onMedia = (e) => narrow.value = e.matches;
2068
2352
  onMounted(() => query?.addEventListener("change", onMedia));
2069
2353
  onBeforeUnmount(() => query?.removeEventListener("change", onMedia));
2070
- const own = ref6("attention");
2354
+ const own = ref7("attention");
2071
2355
  const current = computed2(() => props.page ?? own.value);
2072
- const editing = ref6(null);
2356
+ const editing = ref7(null);
2073
2357
  const go = (p) => {
2074
2358
  editing.value = null;
2075
2359
  emit("navigate", p);
2076
2360
  if (props.page === void 0) own.value = p;
2077
2361
  drawer.value = false;
2078
2362
  };
2079
- const back = (label) => h15("div", { style: { padding: "24px 40px 0" } }, [
2080
- h15(
2363
+ const back = (label) => h16("div", { style: { padding: "24px 40px 0" } }, [
2364
+ h16(
2081
2365
  "button",
2082
2366
  { type: "button", style: styles.buttonGhost, onClick: () => editing.value = null },
2083
2367
  [icon("back", 14), label]
2084
2368
  )
2085
2369
  ]);
2086
- const missing = (text) => h15("p", { style: { ...styles.status, padding: "32px 40px" } }, text);
2370
+ const missing = (text) => h16("p", { style: { ...styles.status, padding: "32px 40px" } }, text);
2087
2371
  const body = () => {
2088
2372
  const c = props.client;
2089
2373
  switch (current.value) {
2090
2374
  case "attention":
2091
- return h15(AttentionScreen, { client: c });
2375
+ return h16(AttentionScreen, { client: c });
2092
2376
  case "modules":
2093
- return h15(ModulesScreen, { client: c });
2377
+ return h16(ModulesScreen, { client: c });
2094
2378
  case "doctor":
2095
- return h15(DoctorScreen, { client: c });
2379
+ return h16(DoctorScreen, { client: c });
2096
2380
  case "environment":
2097
- return h15(EnvironmentScreen, { client: c });
2381
+ return h16(EnvironmentScreen, { client: c });
2098
2382
  case "routes":
2099
- return h15(RoutesScreen, { client: c });
2383
+ return h16(RoutesScreen, { client: c });
2100
2384
  case "tokens":
2101
- return h15(TokensScreen, { client: c });
2385
+ return h16(TokensScreen, { client: c });
2386
+ case "operators":
2387
+ return h16(OperatorsScreen, {
2388
+ client: c,
2389
+ ...props.currentOperator ? { me: props.currentOperator } : {}
2390
+ });
2102
2391
  case "migrations":
2103
- return h15(MigrationsScreen, { client: c });
2392
+ return h16(MigrationsScreen, { client: c });
2104
2393
  case "log":
2105
- return h15(AdminLogScreen, { client: c });
2394
+ return h16(AdminLogScreen, { client: c });
2106
2395
  case "catalog":
2107
- return props.billingClient ? h15(CatalogScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to see the catalog here.");
2396
+ return props.billingClient ? h16(CatalogScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to see the catalog here.");
2108
2397
  case "subscriber":
2109
- return props.billingClient ? h15(SubscriberScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to look up subscribers here.");
2398
+ return props.billingClient ? h16(SubscriberScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to look up subscribers here.");
2110
2399
  case "audit":
2111
- return props.auditClient ? h15(AuditScreen, { client: props.auditClient }) : missing("Pass an AuditAdminClient to see the audit trail here.");
2400
+ return props.auditClient ? h16(AuditScreen, { client: props.auditClient }) : missing("Pass an AuditAdminClient to see the audit trail here.");
2112
2401
  case "users":
2113
- return props.authClient ? h15(UsersScreen, { client: props.authClient }) : missing("Pass an AuthAdminClient to look up users here.");
2402
+ return props.authClient ? h16(UsersScreen, { client: props.authClient }) : missing("Pass an AuthAdminClient to look up users here.");
2114
2403
  case "settings": {
2115
2404
  const cc = props.configClient;
2116
2405
  if (!cc) return missing("Pass a ConfigAdminClient to manage config and secrets here.");
@@ -2118,7 +2407,7 @@ var AdminShell = defineComponent13({
2118
2407
  if (e && e.kind !== "template")
2119
2408
  return [
2120
2409
  back("Config & secrets"),
2121
- h15(ConfigEditorScreen, {
2410
+ h16(ConfigEditorScreen, {
2122
2411
  client: cc,
2123
2412
  kind: e.kind,
2124
2413
  configKey: e.key,
@@ -2126,7 +2415,7 @@ var AdminShell = defineComponent13({
2126
2415
  onSaved: () => editing.value = null
2127
2416
  })
2128
2417
  ];
2129
- return h15(ConfigListScreen, {
2418
+ return h16(ConfigListScreen, {
2130
2419
  client: cc,
2131
2420
  ...props.environment !== void 0 ? { environment: props.environment } : {},
2132
2421
  "onSelect-config": (key) => editing.value = { kind: "config", key },
@@ -2143,27 +2432,27 @@ var AdminShell = defineComponent13({
2143
2432
  if (e?.kind === "template")
2144
2433
  return [
2145
2434
  back("Templates"),
2146
- h15(TemplateEditorScreen, {
2435
+ h16(TemplateEditorScreen, {
2147
2436
  client: kc,
2148
2437
  type: e.type,
2149
2438
  onSaved: () => editing.value = null
2150
2439
  })
2151
2440
  ];
2152
- return h15(TemplateListScreen, {
2441
+ return h16(TemplateListScreen, {
2153
2442
  client: kc,
2154
2443
  "onSelect-template": (t2) => editing.value = { kind: "template", type: t2.type }
2155
2444
  });
2156
2445
  }
2157
2446
  }
2158
2447
  };
2159
- const visible = (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);
2448
+ const visible = (i) => !i.needs || (i.needs === "operators" ? props.operators : i.needs === "config" ? props.configClient : i.needs === "courier" ? props.courierClient : i.needs === "auth" ? props.authClient : i.needs === "billing" ? props.billingClient : props.auditClient);
2160
2449
  const brand = () => {
2161
2450
  const production = props.envLabel ? /^prod/i.test(props.envLabel) : false;
2162
- return h15("div", { style: styles.navBrand }, [
2163
- h15("div", { style: styles.navMark }, [icon("mark", 15)]),
2164
- h15("div", { style: { minWidth: 0, flex: 1 } }, [
2165
- h15("div", { style: styles.navBrandTitle }, props.appName),
2166
- props.envLabel ? h15(
2451
+ return h16("div", { style: styles.navBrand }, [
2452
+ h16("div", { style: styles.navMark }, [icon("mark", 15)]),
2453
+ h16("div", { style: { minWidth: 0, flex: 1 } }, [
2454
+ h16("div", { style: styles.navBrandTitle }, props.appName),
2455
+ props.envLabel ? h16(
2167
2456
  "span",
2168
2457
  {
2169
2458
  style: {
@@ -2180,7 +2469,7 @@ var AdminShell = defineComponent13({
2180
2469
  props.envLabel
2181
2470
  ) : null
2182
2471
  ]),
2183
- narrow.value ? h15(
2472
+ narrow.value ? h16(
2184
2473
  "button",
2185
2474
  {
2186
2475
  type: "button",
@@ -2192,7 +2481,7 @@ var AdminShell = defineComponent13({
2192
2481
  ) : null
2193
2482
  ]);
2194
2483
  };
2195
- const nav = () => h15(
2484
+ const nav = () => h16(
2196
2485
  "nav",
2197
2486
  {
2198
2487
  style: {
@@ -2203,17 +2492,17 @@ var AdminShell = defineComponent13({
2203
2492
  },
2204
2493
  [
2205
2494
  brand(),
2206
- h15(
2495
+ h16(
2207
2496
  "div",
2208
2497
  { style: styles.navScroll },
2209
2498
  NAV.map((g) => {
2210
2499
  const items = g.items.filter(visible);
2211
2500
  if (items.length === 0) return null;
2212
- return h15("div", { key: g.group }, [
2213
- h15("div", { style: styles.navGroup }, g.group),
2501
+ return h16("div", { key: g.group }, [
2502
+ h16("div", { style: styles.navGroup }, g.group),
2214
2503
  ...items.map((i) => {
2215
2504
  const active = current.value === i.page;
2216
- return h15(
2505
+ return h16(
2217
2506
  "button",
2218
2507
  {
2219
2508
  key: i.page,
@@ -2229,18 +2518,18 @@ var AdminShell = defineComponent13({
2229
2518
  ]);
2230
2519
  })
2231
2520
  ),
2232
- slots["footer"] ? h15("div", { style: styles.navFooter }, slots["footer"]()) : null
2521
+ slots["footer"] ? h16("div", { style: styles.navFooter }, slots["footer"]()) : null
2233
2522
  ]
2234
2523
  );
2235
2524
  return () => {
2236
2525
  const title = NAV.flatMap((g) => g.items).find((i) => i.page === current.value)?.label ?? "";
2237
- return h15(
2526
+ return h16(
2238
2527
  "div",
2239
2528
  { style: { ...styles.shell, ...narrow.value ? { flexDirection: "column" } : {} } },
2240
2529
  [
2241
2530
  ...narrow.value ? [
2242
- h15("div", { style: styles.topbar }, [
2243
- h15(
2531
+ h16("div", { style: styles.topbar }, [
2532
+ h16(
2244
2533
  "button",
2245
2534
  {
2246
2535
  type: "button",
@@ -2251,19 +2540,19 @@ var AdminShell = defineComponent13({
2251
2540
  },
2252
2541
  [icon("menu")]
2253
2542
  ),
2254
- h15("span", { style: { fontWeight: 600, fontSize: "14px" } }, title),
2255
- props.envLabel ? h15("span", { style: { ...styles.badge, marginLeft: "auto" } }, props.envLabel) : null
2543
+ h16("span", { style: { fontWeight: 600, fontSize: "14px" } }, title),
2544
+ props.envLabel ? h16("span", { style: { ...styles.badge, marginLeft: "auto" } }, props.envLabel) : null
2256
2545
  ]),
2257
- drawer.value ? h15("div", { style: styles.drawer }, [
2546
+ drawer.value ? h16("div", { style: styles.drawer }, [
2258
2547
  nav(),
2259
- h15("div", {
2548
+ h16("div", {
2260
2549
  style: styles.scrim,
2261
2550
  "aria-hidden": "true",
2262
2551
  onClick: () => drawer.value = false
2263
2552
  })
2264
2553
  ]) : null
2265
2554
  ] : [nav()],
2266
- h15("main", { class: "fonderie-admin-main", style: styles.main }, body())
2555
+ h16("main", { class: "fonderie-admin-main", style: styles.main }, body())
2267
2556
  ]
2268
2557
  );
2269
2558
  };
@@ -2279,6 +2568,7 @@ export {
2279
2568
  EnvironmentScreen,
2280
2569
  MigrationsScreen,
2281
2570
  ModulesScreen,
2571
+ OperatorsScreen,
2282
2572
  RoutesScreen,
2283
2573
  SubscriberScreen,
2284
2574
  TokensScreen,