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