@fonderie/vue-admin-screens 1.1.0 → 1.3.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 +845 -404
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -4
- package/dist/index.d.ts +111 -4
- package/dist/index.js +830 -391
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -29,7 +29,9 @@ __export(index_exports, {
|
|
|
29
29
|
EnvironmentScreen: () => EnvironmentScreen,
|
|
30
30
|
MigrationsScreen: () => MigrationsScreen,
|
|
31
31
|
ModulesScreen: () => ModulesScreen,
|
|
32
|
+
OperatorsScreen: () => OperatorsScreen,
|
|
32
33
|
RoutesScreen: () => RoutesScreen,
|
|
34
|
+
SubscriberBilling: () => SubscriberBilling,
|
|
33
35
|
SubscriberScreen: () => SubscriberScreen,
|
|
34
36
|
TokensScreen: () => TokensScreen,
|
|
35
37
|
UsersScreen: () => UsersScreen
|
|
@@ -39,7 +41,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
39
41
|
// src/screens/AdminShell.ts
|
|
40
42
|
var import_vue_config_admin_screens = require("@fonderie/vue-config-admin-screens");
|
|
41
43
|
var import_vue_courier_admin_screens = require("@fonderie/vue-courier-admin-screens");
|
|
42
|
-
var
|
|
44
|
+
var import_vue18 = require("vue");
|
|
43
45
|
|
|
44
46
|
// src/styles.ts
|
|
45
47
|
var t = {
|
|
@@ -595,6 +597,12 @@ function empty(title, body, iconName = "check") {
|
|
|
595
597
|
body ? (0, import_vue.h)("div", body) : null
|
|
596
598
|
]);
|
|
597
599
|
}
|
|
600
|
+
function refreshButton(onClick, busy, label = "Refresh") {
|
|
601
|
+
return (0, import_vue.h)("button", { type: "button", style: styles.button, onClick, disabled: busy }, [
|
|
602
|
+
icon("refresh", 14),
|
|
603
|
+
busy ? "Working\u2026" : label
|
|
604
|
+
]);
|
|
605
|
+
}
|
|
598
606
|
function methodChip(method) {
|
|
599
607
|
const tone = method === "GET" ? "ok" : method === "DELETE" ? "bad" : method === "POST" ? "info" : "warn";
|
|
600
608
|
const color = toneColor[tone];
|
|
@@ -646,7 +654,7 @@ function table(headers, rows) {
|
|
|
646
654
|
]);
|
|
647
655
|
}
|
|
648
656
|
var td = (children, extra = {}) => (0, import_vue2.h)("td", { style: { ...styles.td, ...extra } }, children);
|
|
649
|
-
var
|
|
657
|
+
var refreshButton2 = (label, disabled, onClick) => (0, import_vue2.h)("button", { type: "button", style: styles.button, disabled, onClick }, [
|
|
650
658
|
icon("refresh", 14),
|
|
651
659
|
disabled ? "Working\u2026" : label
|
|
652
660
|
]);
|
|
@@ -672,7 +680,7 @@ var AdminLogScreen = (0, import_vue3.defineComponent)({
|
|
|
672
680
|
pageHeader(
|
|
673
681
|
"Admin log",
|
|
674
682
|
"Every request made through this surface, newest first \u2014 refused ones included.",
|
|
675
|
-
[
|
|
683
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())]
|
|
676
684
|
),
|
|
677
685
|
error.value ? (0, import_vue3.h)(
|
|
678
686
|
"p",
|
|
@@ -813,7 +821,7 @@ var AttentionScreen = (0, import_vue4.defineComponent)({
|
|
|
813
821
|
)
|
|
814
822
|
];
|
|
815
823
|
},
|
|
816
|
-
[
|
|
824
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())],
|
|
817
825
|
{
|
|
818
826
|
lead: a ? `What needs you on this deployment \xB7 checked ${new Date(a.generatedAt).toLocaleTimeString()}` : "What needs you on this deployment"
|
|
819
827
|
}
|
|
@@ -993,7 +1001,7 @@ var DoctorScreen = (0, import_vue6.defineComponent)({
|
|
|
993
1001
|
report.value.ok ? "ok" : "bad",
|
|
994
1002
|
report.value.ok ? "all checks pass" : "a check failed"
|
|
995
1003
|
) : null,
|
|
996
|
-
|
|
1004
|
+
refreshButton2("Run again", isLoading.value, () => void refresh())
|
|
997
1005
|
],
|
|
998
1006
|
{
|
|
999
1007
|
loadingText: "Running the checks\u2026",
|
|
@@ -1196,7 +1204,7 @@ var MigrationsScreen = (0, import_vue9.defineComponent)({
|
|
|
1196
1204
|
...behind.map((m) => section(m, r.everApplied))
|
|
1197
1205
|
].filter(Boolean);
|
|
1198
1206
|
},
|
|
1199
|
-
[
|
|
1207
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())],
|
|
1200
1208
|
{
|
|
1201
1209
|
lead: "Schema changes each module ships, and whether this database has them.",
|
|
1202
1210
|
errorText: (e) => e.status === 403 ? "Applying migrations needs a token with the write scope." : e.explanation
|
|
@@ -1205,42 +1213,319 @@ var MigrationsScreen = (0, import_vue9.defineComponent)({
|
|
|
1205
1213
|
}
|
|
1206
1214
|
});
|
|
1207
1215
|
|
|
1208
|
-
// src/screens/
|
|
1216
|
+
// src/screens/OperatorsScreen.ts
|
|
1209
1217
|
var import_vue_admin8 = require("@fonderie/vue-admin");
|
|
1210
1218
|
var import_vue10 = require("vue");
|
|
1219
|
+
var LEVELS = [
|
|
1220
|
+
{ label: "Read only", scopes: ["read"], hint: "look around, change nothing" },
|
|
1221
|
+
{ label: "Editor", scopes: ["read", "write"], hint: "change config, templates, users" },
|
|
1222
|
+
{
|
|
1223
|
+
label: "Owner",
|
|
1224
|
+
scopes: ["read", "write", "secrets"],
|
|
1225
|
+
hint: "secrets, tokens and operators too"
|
|
1226
|
+
}
|
|
1227
|
+
];
|
|
1228
|
+
var levelOf = (scopes) => scopes.includes("secrets") ? "Owner" : scopes.includes("write") ? "Editor" : "Read only";
|
|
1229
|
+
var scopesFor = (label) => LEVELS.find((l) => l.label === label)?.scopes ?? ["read"];
|
|
1230
|
+
var small = { height: "28px" };
|
|
1231
|
+
var OperatorsScreen = (0, import_vue10.defineComponent)({
|
|
1232
|
+
name: "FonderieOperatorsScreen",
|
|
1233
|
+
props: {
|
|
1234
|
+
client: { type: Object, required: true },
|
|
1235
|
+
// The signed-in operator's email: their own row offers no disable or
|
|
1236
|
+
// recovery (the server refuses both for yourself anyway).
|
|
1237
|
+
me: { type: String, default: void 0 }
|
|
1238
|
+
},
|
|
1239
|
+
setup(props) {
|
|
1240
|
+
const { report, isLoading, error, invite, recover, update, revokeLink } = (0, import_vue_admin8.useAdminOperators)(
|
|
1241
|
+
props.client
|
|
1242
|
+
);
|
|
1243
|
+
const email = (0, import_vue10.ref)("");
|
|
1244
|
+
const level = (0, import_vue10.ref)("Editor");
|
|
1245
|
+
const minted = (0, import_vue10.ref)(null);
|
|
1246
|
+
const copied = (0, import_vue10.ref)(false);
|
|
1247
|
+
const status = (o) => o.disabledAt ? pill("neutral", "disabled") : o.locked ? pill("warn", "locked") : !o.enrolled ? pill("warn", "setting up") : pill("ok", "active");
|
|
1248
|
+
const mintedNotice = (link) => {
|
|
1249
|
+
const url = `${window.location.origin}${link.url}`;
|
|
1250
|
+
return (0, import_vue10.h)(
|
|
1251
|
+
"div",
|
|
1252
|
+
{
|
|
1253
|
+
style: {
|
|
1254
|
+
...styles.notice,
|
|
1255
|
+
background: "color-mix(in srgb, var(--fonderie-accent,#00d294) 10%, transparent)",
|
|
1256
|
+
borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
|
|
1257
|
+
}
|
|
1258
|
+
},
|
|
1259
|
+
[
|
|
1260
|
+
(0, import_vue10.h)("strong", `Send this link to ${link.email}. It is shown once.`),
|
|
1261
|
+
` It works once and expires ${new Date(link.expiresAt).toLocaleString()}.`,
|
|
1262
|
+
(0, import_vue10.h)(
|
|
1263
|
+
"div",
|
|
1264
|
+
{ style: { display: "flex", gap: "8px", marginTop: "8px", alignItems: "center" } },
|
|
1265
|
+
[
|
|
1266
|
+
(0, import_vue10.h)(
|
|
1267
|
+
"code",
|
|
1268
|
+
{ style: { ...styles.code, flex: 1, padding: "7px 10px", wordBreak: "break-all" } },
|
|
1269
|
+
url
|
|
1270
|
+
),
|
|
1271
|
+
(0, import_vue10.h)(
|
|
1272
|
+
"button",
|
|
1273
|
+
{
|
|
1274
|
+
type: "button",
|
|
1275
|
+
style: styles.button,
|
|
1276
|
+
onClick: () => {
|
|
1277
|
+
void navigator.clipboard?.writeText(url);
|
|
1278
|
+
copied.value = true;
|
|
1279
|
+
}
|
|
1280
|
+
},
|
|
1281
|
+
copied.value ? "Copied" : "Copy"
|
|
1282
|
+
),
|
|
1283
|
+
(0, import_vue10.h)(
|
|
1284
|
+
"button",
|
|
1285
|
+
{
|
|
1286
|
+
type: "button",
|
|
1287
|
+
style: styles.buttonGhost,
|
|
1288
|
+
"aria-label": "Dismiss",
|
|
1289
|
+
onClick: () => {
|
|
1290
|
+
minted.value = null;
|
|
1291
|
+
copied.value = false;
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
[icon("close", 14)]
|
|
1295
|
+
)
|
|
1296
|
+
]
|
|
1297
|
+
)
|
|
1298
|
+
]
|
|
1299
|
+
);
|
|
1300
|
+
};
|
|
1301
|
+
const setMinted = (l) => {
|
|
1302
|
+
copied.value = false;
|
|
1303
|
+
minted.value = l;
|
|
1304
|
+
};
|
|
1305
|
+
return () => {
|
|
1306
|
+
const r = report.value;
|
|
1307
|
+
const e = error.value;
|
|
1308
|
+
return (0, import_vue10.h)("div", { style: styles.container }, [
|
|
1309
|
+
pageHeader(
|
|
1310
|
+
"Operators",
|
|
1311
|
+
"The people who can sign in to this console. Each one uses a password and an authenticator app; there is no sign-up."
|
|
1312
|
+
),
|
|
1313
|
+
e ? (0, import_vue10.h)(
|
|
1314
|
+
"p",
|
|
1315
|
+
{ style: styles.error, role: "alert" },
|
|
1316
|
+
e.status === 403 && e.reason === "FORBIDDEN" ? "Managing operators needs the Owner level." : e.explanation
|
|
1317
|
+
) : null,
|
|
1318
|
+
minted.value ? mintedNotice(minted.value) : null,
|
|
1319
|
+
(0, import_vue10.h)(
|
|
1320
|
+
"form",
|
|
1321
|
+
{
|
|
1322
|
+
style: {
|
|
1323
|
+
...styles.card,
|
|
1324
|
+
display: "flex",
|
|
1325
|
+
gap: "8px",
|
|
1326
|
+
alignItems: "center",
|
|
1327
|
+
flexWrap: "wrap",
|
|
1328
|
+
marginBottom: "20px"
|
|
1329
|
+
},
|
|
1330
|
+
onSubmit: (ev) => {
|
|
1331
|
+
ev.preventDefault();
|
|
1332
|
+
void invite({ email: email.value.trim(), scopes: scopesFor(level.value) }).then((l) => {
|
|
1333
|
+
setMinted(l);
|
|
1334
|
+
email.value = "";
|
|
1335
|
+
}).catch(() => {
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
[
|
|
1340
|
+
icon("users", 16),
|
|
1341
|
+
(0, import_vue10.h)("strong", { style: { fontSize: "13.5px", marginRight: "4px" } }, "Invite"),
|
|
1342
|
+
(0, import_vue10.h)("input", {
|
|
1343
|
+
type: "email",
|
|
1344
|
+
required: true,
|
|
1345
|
+
value: email.value,
|
|
1346
|
+
onInput: (ev) => email.value = ev.target.value,
|
|
1347
|
+
placeholder: "teammate@company.com",
|
|
1348
|
+
style: { ...styles.input, flex: 1, minWidth: "220px" },
|
|
1349
|
+
"aria-label": "Email to invite"
|
|
1350
|
+
}),
|
|
1351
|
+
(0, import_vue10.h)(
|
|
1352
|
+
"select",
|
|
1353
|
+
{
|
|
1354
|
+
value: level.value,
|
|
1355
|
+
onChange: (ev) => level.value = ev.target.value,
|
|
1356
|
+
style: styles.input,
|
|
1357
|
+
"aria-label": "Access level"
|
|
1358
|
+
},
|
|
1359
|
+
LEVELS.map(
|
|
1360
|
+
(l) => (0, import_vue10.h)("option", { key: l.label, value: l.label }, `${l.label} \u2014 ${l.hint}`)
|
|
1361
|
+
)
|
|
1362
|
+
),
|
|
1363
|
+
(0, import_vue10.h)(
|
|
1364
|
+
"button",
|
|
1365
|
+
{ type: "submit", style: styles.buttonPrimary, disabled: !email.value.trim() },
|
|
1366
|
+
[icon("plus", 14), "Create invite link"]
|
|
1367
|
+
)
|
|
1368
|
+
]
|
|
1369
|
+
),
|
|
1370
|
+
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(
|
|
1371
|
+
["Operator", "Access", "Status", "Last sign-in", ""],
|
|
1372
|
+
r.operators.map((o) => {
|
|
1373
|
+
const mine = o.email === props.me;
|
|
1374
|
+
return (0, import_vue10.h)("tr", { key: o.id }, [
|
|
1375
|
+
td([
|
|
1376
|
+
(0, import_vue10.h)("div", { style: { fontWeight: 600 } }, o.name || o.email),
|
|
1377
|
+
o.name ? (0, import_vue10.h)("div", { style: styles.muted }, o.email) : null
|
|
1378
|
+
]),
|
|
1379
|
+
td(
|
|
1380
|
+
(0, import_vue10.h)(
|
|
1381
|
+
"select",
|
|
1382
|
+
{
|
|
1383
|
+
value: levelOf(o.scopes),
|
|
1384
|
+
disabled: mine,
|
|
1385
|
+
onChange: (ev) => void update(o.id, {
|
|
1386
|
+
scopes: scopesFor(ev.target.value)
|
|
1387
|
+
}).catch(() => {
|
|
1388
|
+
}),
|
|
1389
|
+
style: { ...styles.input, ...small, fontSize: "12.5px" },
|
|
1390
|
+
"aria-label": `Access level for ${o.email}`
|
|
1391
|
+
},
|
|
1392
|
+
LEVELS.map((l) => (0, import_vue10.h)("option", { key: l.label, value: l.label }, l.label))
|
|
1393
|
+
)
|
|
1394
|
+
),
|
|
1395
|
+
td([
|
|
1396
|
+
status(o),
|
|
1397
|
+
o.enrolled && o.backupCodesLeft <= 2 && !o.disabledAt ? (0, import_vue10.h)(
|
|
1398
|
+
"div",
|
|
1399
|
+
{ style: { ...styles.muted, marginTop: "4px" } },
|
|
1400
|
+
`${o.backupCodesLeft} backup code(s) left`
|
|
1401
|
+
) : null
|
|
1402
|
+
]),
|
|
1403
|
+
td(o.lastLoginAt ? new Date(o.lastLoginAt).toLocaleString() : "never", {
|
|
1404
|
+
...styles.muted,
|
|
1405
|
+
whiteSpace: "nowrap"
|
|
1406
|
+
}),
|
|
1407
|
+
td(
|
|
1408
|
+
mine ? (0, import_vue10.h)("span", { style: styles.muted }, "you") : [
|
|
1409
|
+
(0, import_vue10.h)(
|
|
1410
|
+
"button",
|
|
1411
|
+
{
|
|
1412
|
+
type: "button",
|
|
1413
|
+
style: { ...styles.button, ...small, marginRight: "6px" },
|
|
1414
|
+
onClick: () => {
|
|
1415
|
+
if (window.confirm(
|
|
1416
|
+
`Create a recovery link for ${o.email}? It signs them out everywhere; the link sets a new password and a new authenticator.`
|
|
1417
|
+
))
|
|
1418
|
+
void recover(o.id).then(setMinted).catch(() => {
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
},
|
|
1422
|
+
"Recovery link"
|
|
1423
|
+
),
|
|
1424
|
+
(0, import_vue10.h)(
|
|
1425
|
+
"button",
|
|
1426
|
+
{
|
|
1427
|
+
type: "button",
|
|
1428
|
+
style: {
|
|
1429
|
+
...o.disabledAt ? styles.button : styles.buttonDanger,
|
|
1430
|
+
...small
|
|
1431
|
+
},
|
|
1432
|
+
onClick: () => {
|
|
1433
|
+
if (o.disabledAt || window.confirm(
|
|
1434
|
+
`Disable ${o.email}? They are signed out immediately.`
|
|
1435
|
+
))
|
|
1436
|
+
void update(o.id, { disabled: !o.disabledAt }).catch(
|
|
1437
|
+
() => {
|
|
1438
|
+
}
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
},
|
|
1442
|
+
o.disabledAt ? "Enable" : "Disable"
|
|
1443
|
+
)
|
|
1444
|
+
],
|
|
1445
|
+
{ textAlign: "right", whiteSpace: "nowrap" }
|
|
1446
|
+
)
|
|
1447
|
+
]);
|
|
1448
|
+
})
|
|
1449
|
+
) : null,
|
|
1450
|
+
r && r.links.length > 0 ? [
|
|
1451
|
+
(0, import_vue10.h)("h2", { style: styles.subtitle }, "Pending links"),
|
|
1452
|
+
(0, import_vue10.h)(
|
|
1453
|
+
"ul",
|
|
1454
|
+
{ style: styles.list },
|
|
1455
|
+
r.links.map(
|
|
1456
|
+
(l) => (0, import_vue10.h)(
|
|
1457
|
+
"li",
|
|
1458
|
+
{
|
|
1459
|
+
key: l.id,
|
|
1460
|
+
style: { ...styles.row, display: "flex", gap: "12px", alignItems: "center" }
|
|
1461
|
+
},
|
|
1462
|
+
[
|
|
1463
|
+
pill(l.kind === "invite" ? "info" : "warn", l.kind, false),
|
|
1464
|
+
(0, import_vue10.h)("span", { style: { flex: 1 } }, [
|
|
1465
|
+
l.email,
|
|
1466
|
+
l.kind === "invite" ? (0, import_vue10.h)("span", { style: styles.muted }, ` \xB7 ${levelOf(l.scopes)}`) : null
|
|
1467
|
+
]),
|
|
1468
|
+
(0, import_vue10.h)(
|
|
1469
|
+
"span",
|
|
1470
|
+
{ style: styles.muted },
|
|
1471
|
+
`expires ${new Date(l.expiresAt).toLocaleString()}`
|
|
1472
|
+
),
|
|
1473
|
+
(0, import_vue10.h)(
|
|
1474
|
+
"button",
|
|
1475
|
+
{
|
|
1476
|
+
type: "button",
|
|
1477
|
+
style: { ...styles.buttonDanger, ...small },
|
|
1478
|
+
onClick: () => void revokeLink(l.id).catch(() => {
|
|
1479
|
+
})
|
|
1480
|
+
},
|
|
1481
|
+
"Revoke"
|
|
1482
|
+
)
|
|
1483
|
+
]
|
|
1484
|
+
)
|
|
1485
|
+
)
|
|
1486
|
+
)
|
|
1487
|
+
] : null
|
|
1488
|
+
]);
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
// src/screens/TokensScreen.ts
|
|
1494
|
+
var import_vue_admin9 = require("@fonderie/vue-admin");
|
|
1495
|
+
var import_vue11 = require("vue");
|
|
1211
1496
|
var ALL = ["read", "write", "secrets"];
|
|
1212
|
-
var TokensScreen = (0,
|
|
1497
|
+
var TokensScreen = (0, import_vue11.defineComponent)({
|
|
1213
1498
|
name: "FonderieTokensScreen",
|
|
1214
1499
|
props: { client: { type: Object, required: true } },
|
|
1215
1500
|
setup(props) {
|
|
1216
|
-
const { report, isLoading, error, issue, revoke } = (0,
|
|
1217
|
-
const name = (0,
|
|
1218
|
-
const scopes = (0,
|
|
1219
|
-
const days = (0,
|
|
1220
|
-
const minted = (0,
|
|
1501
|
+
const { report, isLoading, error, issue, revoke } = (0, import_vue_admin9.useAdminTokens)(props.client);
|
|
1502
|
+
const name = (0, import_vue11.ref)("");
|
|
1503
|
+
const scopes = (0, import_vue11.ref)(["read"]);
|
|
1504
|
+
const days = (0, import_vue11.ref)("");
|
|
1505
|
+
const minted = (0, import_vue11.ref)(null);
|
|
1221
1506
|
const toggle = (s) => scopes.value = scopes.value.includes(s) ? scopes.value.filter((x) => x !== s) : [...scopes.value, s];
|
|
1222
1507
|
return () => {
|
|
1223
1508
|
const r = report.value;
|
|
1224
|
-
return (0,
|
|
1509
|
+
return (0, import_vue11.h)("div", { style: styles.container }, [
|
|
1225
1510
|
pageHeader(
|
|
1226
|
-
"
|
|
1227
|
-
"
|
|
1511
|
+
"Tokens",
|
|
1512
|
+
"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
1513
|
),
|
|
1229
|
-
error.value ? (0,
|
|
1514
|
+
error.value ? (0, import_vue11.h)(
|
|
1230
1515
|
"p",
|
|
1231
1516
|
{ style: styles.error, role: "alert" },
|
|
1232
1517
|
error.value.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.value.explanation
|
|
1233
1518
|
) : null,
|
|
1234
|
-
(0,
|
|
1235
|
-
r ? (0,
|
|
1519
|
+
(0, import_vue11.h)("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Root token"),
|
|
1520
|
+
r ? (0, import_vue11.h)(
|
|
1236
1521
|
"div",
|
|
1237
1522
|
{ style: { ...styles.card, display: "flex", gap: "12px", alignItems: "flex-start" } },
|
|
1238
1523
|
[
|
|
1239
1524
|
icon("lock", 18, { marginTop: "2px" }),
|
|
1240
|
-
(0,
|
|
1525
|
+
(0, import_vue11.h)("div", [
|
|
1241
1526
|
pill(r.admin.ok ? "ok" : "bad", r.admin.ok ? "strong" : "weak"),
|
|
1242
1527
|
...r.admin.problems.map(
|
|
1243
|
-
(p) => (0,
|
|
1528
|
+
(p) => (0, import_vue11.h)(
|
|
1244
1529
|
"div",
|
|
1245
1530
|
{
|
|
1246
1531
|
key: p.message,
|
|
@@ -1256,13 +1541,13 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1256
1541
|
])
|
|
1257
1542
|
]
|
|
1258
1543
|
) : null,
|
|
1259
|
-
(0,
|
|
1260
|
-
r?.issued === null ? (0,
|
|
1544
|
+
(0, import_vue11.h)("h2", { style: styles.subtitle }, "Issued tokens"),
|
|
1545
|
+
r?.issued === null ? (0, import_vue11.h)(
|
|
1261
1546
|
"p",
|
|
1262
1547
|
{ style: styles.muted },
|
|
1263
1548
|
"Issuing is off \u2014 give AdminModule a store and run its migrations."
|
|
1264
1549
|
) : [
|
|
1265
|
-
minted.value ? (0,
|
|
1550
|
+
minted.value ? (0, import_vue11.h)(
|
|
1266
1551
|
"div",
|
|
1267
1552
|
{
|
|
1268
1553
|
style: {
|
|
@@ -1272,9 +1557,9 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1272
1557
|
}
|
|
1273
1558
|
},
|
|
1274
1559
|
[
|
|
1275
|
-
(0,
|
|
1560
|
+
(0, import_vue11.h)("strong", "Copy this now \u2014 it is never shown again."),
|
|
1276
1561
|
` (${minted.value.name})`,
|
|
1277
|
-
(0,
|
|
1562
|
+
(0, import_vue11.h)(
|
|
1278
1563
|
"div",
|
|
1279
1564
|
{
|
|
1280
1565
|
style: {
|
|
@@ -1289,7 +1574,7 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1289
1574
|
)
|
|
1290
1575
|
]
|
|
1291
1576
|
) : null,
|
|
1292
|
-
(0,
|
|
1577
|
+
(0, import_vue11.h)(
|
|
1293
1578
|
"form",
|
|
1294
1579
|
{
|
|
1295
1580
|
style: styles.toolbar,
|
|
@@ -1309,7 +1594,7 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1309
1594
|
}
|
|
1310
1595
|
},
|
|
1311
1596
|
[
|
|
1312
|
-
(0,
|
|
1597
|
+
(0, import_vue11.h)("input", {
|
|
1313
1598
|
value: name.value,
|
|
1314
1599
|
onInput: (e) => name.value = e.target.value,
|
|
1315
1600
|
placeholder: "name",
|
|
@@ -1317,7 +1602,7 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1317
1602
|
"aria-label": "Name"
|
|
1318
1603
|
}),
|
|
1319
1604
|
...ALL.map(
|
|
1320
|
-
(s) => (0,
|
|
1605
|
+
(s) => (0, import_vue11.h)(
|
|
1321
1606
|
"label",
|
|
1322
1607
|
{
|
|
1323
1608
|
key: s,
|
|
@@ -1331,7 +1616,7 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1331
1616
|
}
|
|
1332
1617
|
},
|
|
1333
1618
|
[
|
|
1334
|
-
(0,
|
|
1619
|
+
(0, import_vue11.h)("input", {
|
|
1335
1620
|
type: "checkbox",
|
|
1336
1621
|
checked: scopes.value.includes(s),
|
|
1337
1622
|
onChange: () => toggle(s)
|
|
@@ -1340,14 +1625,14 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1340
1625
|
]
|
|
1341
1626
|
)
|
|
1342
1627
|
),
|
|
1343
|
-
(0,
|
|
1628
|
+
(0, import_vue11.h)("input", {
|
|
1344
1629
|
value: days.value,
|
|
1345
1630
|
onInput: (e) => days.value = e.target.value,
|
|
1346
1631
|
placeholder: "days (optional)",
|
|
1347
1632
|
style: { ...styles.input, width: "140px" },
|
|
1348
1633
|
"aria-label": "Days"
|
|
1349
1634
|
}),
|
|
1350
|
-
(0,
|
|
1635
|
+
(0, import_vue11.h)(
|
|
1351
1636
|
"button",
|
|
1352
1637
|
{
|
|
1353
1638
|
type: "submit",
|
|
@@ -1361,7 +1646,7 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1361
1646
|
r?.issued && r.issued.length > 0 ? table(
|
|
1362
1647
|
["Name", "Scopes", "Created", "Expires", "Last used", ""],
|
|
1363
1648
|
r.issued.map(
|
|
1364
|
-
(t2) => (0,
|
|
1649
|
+
(t2) => (0, import_vue11.h)("tr", { key: t2.id }, [
|
|
1365
1650
|
td([t2.name, t2.revokedAt ? [" ", pill("neutral", "revoked")] : ""]),
|
|
1366
1651
|
td(t2.scopes.join(", "), styles.mono),
|
|
1367
1652
|
td(
|
|
@@ -1377,7 +1662,7 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1377
1662
|
styles.muted
|
|
1378
1663
|
),
|
|
1379
1664
|
td(
|
|
1380
|
-
t2.revokedAt ? "" : (0,
|
|
1665
|
+
t2.revokedAt ? "" : (0, import_vue11.h)(
|
|
1381
1666
|
"button",
|
|
1382
1667
|
{
|
|
1383
1668
|
type: "button",
|
|
@@ -1401,19 +1686,19 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1401
1686
|
"tokens"
|
|
1402
1687
|
)
|
|
1403
1688
|
],
|
|
1404
|
-
(0,
|
|
1405
|
-
r && r.legacy.length === 0 ? (0,
|
|
1689
|
+
(0, import_vue11.h)("h2", { style: styles.subtitle }, "Legacy per-brick tokens"),
|
|
1690
|
+
r && r.legacy.length === 0 ? (0, import_vue11.h)(
|
|
1406
1691
|
"p",
|
|
1407
1692
|
{ style: styles.muted },
|
|
1408
1693
|
"None \u2014 every brick's admin surface goes through this token."
|
|
1409
|
-
) : (0,
|
|
1694
|
+
) : (0, import_vue11.h)(
|
|
1410
1695
|
"ul",
|
|
1411
1696
|
{ style: styles.list },
|
|
1412
1697
|
(r?.legacy ?? []).map(
|
|
1413
|
-
(l) => (0,
|
|
1414
|
-
(0,
|
|
1698
|
+
(l) => (0, import_vue11.h)("li", { key: l.module, style: styles.row }, [
|
|
1699
|
+
(0, import_vue11.h)("span", { style: styles.mono }, l.module),
|
|
1415
1700
|
" ",
|
|
1416
|
-
(0,
|
|
1701
|
+
(0, import_vue11.h)(
|
|
1417
1702
|
"span",
|
|
1418
1703
|
{ style: styles.muted },
|
|
1419
1704
|
"still registers its standalone routes with its own token (deprecated)"
|
|
@@ -1428,29 +1713,234 @@ var TokensScreen = (0, import_vue10.defineComponent)({
|
|
|
1428
1713
|
|
|
1429
1714
|
// src/screens/UsersScreen.ts
|
|
1430
1715
|
var import_client = require("@fonderie/client");
|
|
1431
|
-
var
|
|
1432
|
-
var
|
|
1433
|
-
|
|
1716
|
+
var import_vue_admin11 = require("@fonderie/vue-admin");
|
|
1717
|
+
var import_vue14 = require("vue");
|
|
1718
|
+
|
|
1719
|
+
// src/billing.ts
|
|
1720
|
+
var import_vue12 = require("vue");
|
|
1721
|
+
function periodEnd(s) {
|
|
1722
|
+
if (!s.currentPeriodEnd) return null;
|
|
1723
|
+
const date = new Date(s.currentPeriodEnd).toLocaleDateString();
|
|
1724
|
+
if (s.status === "canceled")
|
|
1725
|
+
return { label: new Date(s.currentPeriodEnd) < /* @__PURE__ */ new Date() ? "Ended" : "Ends", date };
|
|
1726
|
+
if (s.cancelAtPeriodEnd) return { label: "Ends", date };
|
|
1727
|
+
return { label: "Renews", date };
|
|
1728
|
+
}
|
|
1729
|
+
var statusTone = (status) => status === "active" ? "ok" : status === "trialing" ? "info" : status === "past_due" || status === "unpaid" ? "warn" : "neutral";
|
|
1730
|
+
function useSubscriptionIndex(client) {
|
|
1731
|
+
const index = (0, import_vue12.ref)(null);
|
|
1732
|
+
if (!client) return index;
|
|
1733
|
+
void (async () => {
|
|
1734
|
+
const map = /* @__PURE__ */ new Map();
|
|
1735
|
+
let cursor;
|
|
1736
|
+
for (let page2 = 0; page2 < 20; page2++) {
|
|
1737
|
+
const { result } = await client.listSubscriptions({
|
|
1738
|
+
limit: 100,
|
|
1739
|
+
...cursor ? { cursor } : {}
|
|
1740
|
+
});
|
|
1741
|
+
for (const s of result.subscriptions) {
|
|
1742
|
+
const key = `${s.subscriberType}/${s.subscriberId}`;
|
|
1743
|
+
if (!map.has(key)) map.set(key, s);
|
|
1744
|
+
}
|
|
1745
|
+
if (!result.nextCursor) break;
|
|
1746
|
+
cursor = result.nextCursor;
|
|
1747
|
+
}
|
|
1748
|
+
index.value = map;
|
|
1749
|
+
})().catch(() => {
|
|
1750
|
+
index.value = /* @__PURE__ */ new Map();
|
|
1751
|
+
});
|
|
1752
|
+
return index;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// src/screens/SubscriberBilling.ts
|
|
1756
|
+
var import_vue_admin10 = require("@fonderie/vue-admin");
|
|
1757
|
+
var import_vue13 = require("vue");
|
|
1758
|
+
var SubscriberBilling = (0, import_vue13.defineComponent)({
|
|
1759
|
+
name: "FonderieSubscriberBilling",
|
|
1760
|
+
props: {
|
|
1761
|
+
client: { type: Object, required: true },
|
|
1762
|
+
subscriberType: { type: String, required: true },
|
|
1763
|
+
subscriberId: { type: String, required: true }
|
|
1764
|
+
},
|
|
1765
|
+
setup(props) {
|
|
1766
|
+
const subscriber = (0, import_vue13.computed)(() => ({ type: props.subscriberType, id: props.subscriberId }));
|
|
1767
|
+
const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_vue_admin10.useAdminSubscriber)(props.client, subscriber, { limit: 20 });
|
|
1768
|
+
const amount = (0, import_vue13.ref)("");
|
|
1769
|
+
const note = (0, import_vue13.ref)("");
|
|
1770
|
+
const granted = (0, import_vue13.ref)(null);
|
|
1771
|
+
return () => {
|
|
1772
|
+
const s = subscription.value;
|
|
1773
|
+
const w = wallet.value;
|
|
1774
|
+
if (isLoading.value && !w && !s) return (0, import_vue13.h)("p", { style: styles.status }, "Loading billing\u2026");
|
|
1775
|
+
const end = s ? periodEnd(s) : null;
|
|
1776
|
+
return (0, import_vue13.h)("div", [
|
|
1777
|
+
error.value ? (0, import_vue13.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
1778
|
+
(0, import_vue13.h)("div", { style: { ...styles.grid, marginBottom: "16px" } }, [
|
|
1779
|
+
(0, import_vue13.h)("div", { style: styles.card }, [
|
|
1780
|
+
(0, import_vue13.h)("div", { style: styles.statLabel }, "Plan"),
|
|
1781
|
+
(0, import_vue13.h)(
|
|
1782
|
+
"div",
|
|
1783
|
+
{
|
|
1784
|
+
style: {
|
|
1785
|
+
display: "flex",
|
|
1786
|
+
alignItems: "center",
|
|
1787
|
+
gap: "8px",
|
|
1788
|
+
marginTop: "6px",
|
|
1789
|
+
flexWrap: "wrap"
|
|
1790
|
+
}
|
|
1791
|
+
},
|
|
1792
|
+
[
|
|
1793
|
+
(0, import_vue13.h)("strong", { style: { fontSize: "18px" } }, s ? s.plan : "Free"),
|
|
1794
|
+
...s ? [
|
|
1795
|
+
(0, import_vue13.h)("span", { style: styles.muted }, `\xB7 ${s.interval}`),
|
|
1796
|
+
pill(statusTone(s.status), s.status)
|
|
1797
|
+
] : [(0, import_vue13.h)("span", { style: styles.muted }, "no subscription")]
|
|
1798
|
+
]
|
|
1799
|
+
),
|
|
1800
|
+
end ? (0, import_vue13.h)("div", { style: styles.statHint }, `${end.label} ${end.date}`) : null,
|
|
1801
|
+
s?.providerSubscriptionId ? (0, import_vue13.h)(
|
|
1802
|
+
"div",
|
|
1803
|
+
{ style: { ...styles.statHint, fontFamily: "var(--fonderie-mono,monospace)" } },
|
|
1804
|
+
s.providerSubscriptionId
|
|
1805
|
+
) : null
|
|
1806
|
+
]),
|
|
1807
|
+
(0, import_vue13.h)("div", { style: styles.card }, [
|
|
1808
|
+
(0, import_vue13.h)("div", { style: styles.statLabel }, "Credits"),
|
|
1809
|
+
...w ? [
|
|
1810
|
+
(0, import_vue13.h)("div", { style: styles.statValue }, [
|
|
1811
|
+
`${w.balance} `,
|
|
1812
|
+
(0, import_vue13.h)("span", { style: { fontSize: "14px", fontWeight: 500 } }, w.currency)
|
|
1813
|
+
]),
|
|
1814
|
+
(0, import_vue13.h)(
|
|
1815
|
+
"div",
|
|
1816
|
+
{ style: styles.statHint },
|
|
1817
|
+
`${w.purchased ?? "0"} permanent \xB7 ${w.granted ?? "0"} plan allowance${w.grantedExpiresAt ? ` (expires ${new Date(w.grantedExpiresAt).toLocaleDateString()})` : ""}`
|
|
1818
|
+
)
|
|
1819
|
+
] : [
|
|
1820
|
+
(0, import_vue13.h)(
|
|
1821
|
+
"div",
|
|
1822
|
+
{ style: styles.statHint },
|
|
1823
|
+
"No wallet \u2014 billing has no wallet configuration."
|
|
1824
|
+
)
|
|
1825
|
+
]
|
|
1826
|
+
])
|
|
1827
|
+
]),
|
|
1828
|
+
w ? (0, import_vue13.h)(
|
|
1829
|
+
"form",
|
|
1830
|
+
{
|
|
1831
|
+
style: {
|
|
1832
|
+
...styles.card,
|
|
1833
|
+
display: "flex",
|
|
1834
|
+
gap: "8px",
|
|
1835
|
+
alignItems: "center",
|
|
1836
|
+
flexWrap: "wrap",
|
|
1837
|
+
marginBottom: "16px"
|
|
1838
|
+
},
|
|
1839
|
+
onSubmit: (e) => {
|
|
1840
|
+
e.preventDefault();
|
|
1841
|
+
const key = `admin-grant-${props.subscriberType}-${props.subscriberId}-${Date.now()}`;
|
|
1842
|
+
const a = amount.value.trim();
|
|
1843
|
+
void grant({
|
|
1844
|
+
amount: a,
|
|
1845
|
+
...note.value ? { description: note.value } : {},
|
|
1846
|
+
idempotencyKey: key
|
|
1847
|
+
}).then(() => {
|
|
1848
|
+
granted.value = `Granted ${a} ${w.currency}.`;
|
|
1849
|
+
amount.value = "";
|
|
1850
|
+
note.value = "";
|
|
1851
|
+
}).catch(() => {
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
},
|
|
1855
|
+
[
|
|
1856
|
+
(0, import_vue13.h)("strong", { style: { fontSize: "13.5px", marginRight: "4px" } }, "Grant credits"),
|
|
1857
|
+
(0, import_vue13.h)("input", {
|
|
1858
|
+
value: amount.value,
|
|
1859
|
+
onInput: (e) => amount.value = e.target.value,
|
|
1860
|
+
placeholder: `amount (${w.currency})`,
|
|
1861
|
+
inputmode: "numeric",
|
|
1862
|
+
style: { ...styles.input, width: "150px" },
|
|
1863
|
+
"aria-label": "Amount"
|
|
1864
|
+
}),
|
|
1865
|
+
(0, import_vue13.h)("input", {
|
|
1866
|
+
value: note.value,
|
|
1867
|
+
onInput: (e) => note.value = e.target.value,
|
|
1868
|
+
placeholder: "reason (optional)",
|
|
1869
|
+
style: { ...styles.input, flex: 1, minWidth: "180px" },
|
|
1870
|
+
"aria-label": "Reason"
|
|
1871
|
+
}),
|
|
1872
|
+
(0, import_vue13.h)(
|
|
1873
|
+
"button",
|
|
1874
|
+
{
|
|
1875
|
+
type: "submit",
|
|
1876
|
+
style: styles.buttonPrimary,
|
|
1877
|
+
disabled: !/^\d+$/.test(amount.value.trim())
|
|
1878
|
+
},
|
|
1879
|
+
[icon("plus", 14), "Grant"]
|
|
1880
|
+
),
|
|
1881
|
+
granted.value ? (0, import_vue13.h)("span", { style: styles.ok }, granted.value) : null
|
|
1882
|
+
]
|
|
1883
|
+
) : null,
|
|
1884
|
+
w ? ledger.value.length === 0 ? empty("No credit movements yet", void 0, "subscriber") : (0, import_vue13.h)("div", [
|
|
1885
|
+
table(
|
|
1886
|
+
["When", "Kind", "Amount", "Note"],
|
|
1887
|
+
ledger.value.map(
|
|
1888
|
+
(t2) => (0, import_vue13.h)("tr", { key: t2.id }, [
|
|
1889
|
+
td(new Date(t2.createdAt).toLocaleString(), {
|
|
1890
|
+
...styles.muted,
|
|
1891
|
+
whiteSpace: "nowrap"
|
|
1892
|
+
}),
|
|
1893
|
+
td(t2.type),
|
|
1894
|
+
td(t2.amount, styles.mono),
|
|
1895
|
+
td(t2.description ?? "", styles.muted)
|
|
1896
|
+
])
|
|
1897
|
+
)
|
|
1898
|
+
),
|
|
1899
|
+
hasMoreLedger.value ? loadMoreButton(() => void loadMoreLedger()) : null
|
|
1900
|
+
]) : null
|
|
1901
|
+
]);
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
|
|
1906
|
+
// src/screens/UsersScreen.ts
|
|
1907
|
+
var UsersScreen = (0, import_vue14.defineComponent)({
|
|
1434
1908
|
name: "FonderieUsersScreen",
|
|
1435
1909
|
props: {
|
|
1436
1910
|
client: { type: Object, required: true },
|
|
1437
|
-
pageSize: { type: Number, default: 50 }
|
|
1911
|
+
pageSize: { type: Number, default: 50 },
|
|
1912
|
+
// Given ⇒ a Plan column, and each user's plan, credits and grant form on
|
|
1913
|
+
// their page. Everyone has a wallet; no subscription means the free tier.
|
|
1914
|
+
billingClient: { type: Object, default: void 0 },
|
|
1915
|
+
// Open this user on arrival (e.g. from the Subscriptions list).
|
|
1916
|
+
openUserId: { type: String, default: void 0 }
|
|
1438
1917
|
},
|
|
1439
1918
|
setup(props) {
|
|
1440
|
-
const input = (0,
|
|
1441
|
-
const email = (0,
|
|
1442
|
-
const selectedId = (0,
|
|
1443
|
-
const
|
|
1444
|
-
const
|
|
1919
|
+
const input = (0, import_vue14.ref)("");
|
|
1920
|
+
const email = (0, import_vue14.ref)("");
|
|
1921
|
+
const selectedId = (0, import_vue14.ref)(props.openUserId ?? "");
|
|
1922
|
+
const plans = useSubscriptionIndex(props.billingClient);
|
|
1923
|
+
const list = (0, import_vue_admin11.useAdminUsers)(props.client, { limit: props.pageSize });
|
|
1924
|
+
const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_vue_admin11.useAdminUser)(
|
|
1445
1925
|
props.client,
|
|
1446
1926
|
{ email, id: selectedId }
|
|
1447
1927
|
);
|
|
1448
|
-
const userId = (0,
|
|
1449
|
-
const sessions = (0,
|
|
1450
|
-
const history = (0,
|
|
1451
|
-
const showList = (0,
|
|
1928
|
+
const userId = (0, import_vue14.computed)(() => user.value?.id ?? null);
|
|
1929
|
+
const sessions = (0, import_vue_admin11.useAdminUserSessions)(props.client, userId);
|
|
1930
|
+
const history = (0, import_vue_admin11.useAdminLoginHistory)(props.client, userId, { limit: 20 });
|
|
1931
|
+
const showList = (0, import_vue14.computed)(() => !email.value && !selectedId.value);
|
|
1452
1932
|
const yesNo = (v) => v ? pill("ok", "yes") : pill("neutral", "no");
|
|
1453
|
-
const row = (k, v) => (0,
|
|
1933
|
+
const row = (k, v) => (0, import_vue14.h)("tr", [td(k), td(v)]);
|
|
1934
|
+
const planCell = (id) => {
|
|
1935
|
+
if (!plans.value) return (0, import_vue14.h)("span", { style: styles.muted }, "\u2026");
|
|
1936
|
+
const sub = plans.value.get(`user/${id}`);
|
|
1937
|
+
if (!sub || sub.status === "canceled") return (0, import_vue14.h)("span", { style: styles.muted }, "free");
|
|
1938
|
+
return [
|
|
1939
|
+
(0, import_vue14.h)("strong", sub.plan),
|
|
1940
|
+
" ",
|
|
1941
|
+
sub.status !== "active" ? pill(statusTone(sub.status), sub.status) : null
|
|
1942
|
+
];
|
|
1943
|
+
};
|
|
1454
1944
|
const clear = () => {
|
|
1455
1945
|
email.value = "";
|
|
1456
1946
|
selectedId.value = "";
|
|
@@ -1458,12 +1948,12 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1458
1948
|
};
|
|
1459
1949
|
return () => {
|
|
1460
1950
|
const u = user.value;
|
|
1461
|
-
return (0,
|
|
1951
|
+
return (0, import_vue14.h)("div", { style: styles.container }, [
|
|
1462
1952
|
pageHeader(
|
|
1463
1953
|
"Users",
|
|
1464
1954
|
"Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
|
|
1465
1955
|
),
|
|
1466
|
-
(0,
|
|
1956
|
+
(0, import_vue14.h)(
|
|
1467
1957
|
"form",
|
|
1468
1958
|
{
|
|
1469
1959
|
style: styles.toolbar,
|
|
@@ -1474,7 +1964,7 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1474
1964
|
}
|
|
1475
1965
|
},
|
|
1476
1966
|
[
|
|
1477
|
-
(0,
|
|
1967
|
+
(0, import_vue14.h)("input", {
|
|
1478
1968
|
type: "email",
|
|
1479
1969
|
value: input.value,
|
|
1480
1970
|
onInput: (e) => input.value = e.target.value,
|
|
@@ -1482,22 +1972,22 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1482
1972
|
style: { ...styles.input, minWidth: "280px" },
|
|
1483
1973
|
"aria-label": "Email"
|
|
1484
1974
|
}),
|
|
1485
|
-
(0,
|
|
1975
|
+
(0, import_vue14.h)(
|
|
1486
1976
|
"button",
|
|
1487
1977
|
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1488
1978
|
[icon("search", 14), "Look up"]
|
|
1489
1979
|
),
|
|
1490
|
-
showList.value ?
|
|
1980
|
+
showList.value ? refreshButton2("Refresh", list.isLoading.value, () => void list.refresh()) : actionButton([icon("back", 14), "All users"], false, clear, styles.buttonGhost)
|
|
1491
1981
|
]
|
|
1492
1982
|
),
|
|
1493
|
-
showList.value ? (0,
|
|
1494
|
-
list.error.value ? (0,
|
|
1983
|
+
showList.value ? (0, import_vue14.h)("div", [
|
|
1984
|
+
list.error.value ? (0, import_vue14.h)("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
1495
1985
|
list.users.value.length === 0 && !list.isLoading.value ? empty("No users yet", "Sign-ups appear here as they happen.", "users") : table(
|
|
1496
|
-
["Email", "Name", "Created", "Status"],
|
|
1986
|
+
props.billingClient ? ["Email", "Name", "Plan", "Created", "Status"] : ["Email", "Name", "Created", "Status"],
|
|
1497
1987
|
list.users.value.map(
|
|
1498
|
-
(u2) => (0,
|
|
1988
|
+
(u2) => (0, import_vue14.h)("tr", { key: u2.id }, [
|
|
1499
1989
|
td(
|
|
1500
|
-
(0,
|
|
1990
|
+
(0, import_vue14.h)(
|
|
1501
1991
|
"button",
|
|
1502
1992
|
{
|
|
1503
1993
|
type: "button",
|
|
@@ -1508,8 +1998,9 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1508
1998
|
)
|
|
1509
1999
|
),
|
|
1510
2000
|
td(
|
|
1511
|
-
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || (0,
|
|
2001
|
+
`${u2.firstName ?? ""} ${u2.lastName ?? ""}`.trim() || (0, import_vue14.h)("span", { style: styles.muted }, "\u2014")
|
|
1512
2002
|
),
|
|
2003
|
+
props.billingClient ? td(planCell(u2.id)) : null,
|
|
1513
2004
|
td(new Date(u2.createdAt).toLocaleDateString()),
|
|
1514
2005
|
td(
|
|
1515
2006
|
u2.suspended ? pill("warn", "suspended") : u2.deletedAt ? pill("neutral", "deleted") : pill("ok", "active")
|
|
@@ -1517,24 +2008,24 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1517
2008
|
])
|
|
1518
2009
|
)
|
|
1519
2010
|
),
|
|
1520
|
-
list.isLoading.value ? (0,
|
|
2011
|
+
list.isLoading.value ? (0, import_vue14.h)("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1521
2012
|
list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
|
|
1522
2013
|
]) : null,
|
|
1523
|
-
error.value ? (0,
|
|
2014
|
+
error.value ? (0, import_vue14.h)(
|
|
1524
2015
|
"p",
|
|
1525
2016
|
{ style: styles.error, role: "alert" },
|
|
1526
2017
|
error.value.status === 404 ? "No user with that email." : error.value.explanation
|
|
1527
2018
|
) : null,
|
|
1528
2019
|
u ? [
|
|
1529
|
-
(0,
|
|
2020
|
+
(0, import_vue14.h)("h2", { style: styles.subtitle }, [
|
|
1530
2021
|
u.firstName || u.lastName ? `${u.firstName} ${u.lastName}`.trim() : u.email,
|
|
1531
2022
|
" ",
|
|
1532
2023
|
u.suspended ? pill("warn", "suspended") : null,
|
|
1533
2024
|
u.deletedAt ? pill("neutral", "deleted") : null
|
|
1534
2025
|
]),
|
|
1535
|
-
(0,
|
|
1536
|
-
(0,
|
|
1537
|
-
row("id", (0,
|
|
2026
|
+
(0, import_vue14.h)("table", { style: styles.table }, [
|
|
2027
|
+
(0, import_vue14.h)("tbody", [
|
|
2028
|
+
row("id", (0, import_vue14.h)("span", { style: styles.mono }, u.id)),
|
|
1538
2029
|
row("email", [u.email, " \xB7 verified ", yesNo(u.isEmailVerified)]),
|
|
1539
2030
|
row("MFA", yesNo(u.mfaEnabled)),
|
|
1540
2031
|
row(
|
|
@@ -1543,12 +2034,12 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1543
2034
|
),
|
|
1544
2035
|
row(
|
|
1545
2036
|
"last login",
|
|
1546
|
-
u.lastLogin ? new Date(u.lastLogin).toLocaleString() : (0,
|
|
2037
|
+
u.lastLogin ? new Date(u.lastLogin).toLocaleString() : (0, import_vue14.h)("span", { style: styles.muted }, "never")
|
|
1547
2038
|
),
|
|
1548
2039
|
row("created", new Date(u.createdAt).toLocaleString())
|
|
1549
2040
|
])
|
|
1550
2041
|
]),
|
|
1551
|
-
(0,
|
|
2042
|
+
(0, import_vue14.h)("div", { style: { ...styles.toolbar, marginTop: "12px" } }, [
|
|
1552
2043
|
u.suspended ? actionButton("Unsuspend", isLoading.value, () => void unsuspend()) : actionButton(
|
|
1553
2044
|
"Suspend",
|
|
1554
2045
|
isLoading.value,
|
|
@@ -1561,19 +2052,26 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1561
2052
|
() => void revokeSessions().then(() => sessions.refresh())
|
|
1562
2053
|
)
|
|
1563
2054
|
]),
|
|
1564
|
-
(0,
|
|
1565
|
-
|
|
2055
|
+
props.billingClient ? (0, import_vue14.h)("h2", { style: styles.subtitle }, "Plan & credits") : null,
|
|
2056
|
+
props.billingClient ? (0, import_vue14.h)(SubscriberBilling, {
|
|
2057
|
+
key: u.id,
|
|
2058
|
+
client: props.billingClient,
|
|
2059
|
+
subscriberType: "user",
|
|
2060
|
+
subscriberId: u.id
|
|
2061
|
+
}) : null,
|
|
2062
|
+
(0, import_vue14.h)("h2", { style: styles.subtitle }, "Live sessions"),
|
|
2063
|
+
sessions.sessions.value.length === 0 ? (0, import_vue14.h)("p", { style: styles.muted }, "None.") : (0, import_vue14.h)(
|
|
1566
2064
|
"ul",
|
|
1567
2065
|
{ style: styles.list },
|
|
1568
2066
|
sessions.sessions.value.map(
|
|
1569
|
-
(s) => (0,
|
|
1570
|
-
(0,
|
|
2067
|
+
(s) => (0, import_vue14.h)("li", { key: s.id, style: styles.row }, [
|
|
2068
|
+
(0, import_vue14.h)("span", { style: styles.mono }, s.ipAddress ?? "\u2014"),
|
|
1571
2069
|
" ",
|
|
1572
|
-
(0,
|
|
2070
|
+
(0, import_vue14.h)("span", { style: styles.muted }, (0, import_client.describeLocation)(s.location) ?? ""),
|
|
1573
2071
|
" ",
|
|
1574
|
-
(0,
|
|
2072
|
+
(0, import_vue14.h)("span", { style: styles.muted }, s.userAgent ?? ""),
|
|
1575
2073
|
" ",
|
|
1576
|
-
(0,
|
|
2074
|
+
(0, import_vue14.h)(
|
|
1577
2075
|
"span",
|
|
1578
2076
|
{ style: styles.muted },
|
|
1579
2077
|
`since ${new Date(s.createdAt).toLocaleString()}`
|
|
@@ -1581,22 +2079,22 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1581
2079
|
])
|
|
1582
2080
|
)
|
|
1583
2081
|
),
|
|
1584
|
-
(0,
|
|
1585
|
-
history.events.value.length === 0 && !history.isLoading.value ? (0,
|
|
2082
|
+
(0, import_vue14.h)("h2", { style: styles.subtitle }, "Recent sign-ins"),
|
|
2083
|
+
history.events.value.length === 0 && !history.isLoading.value ? (0, import_vue14.h)("p", { style: styles.muted }, "None recorded.") : (0, import_vue14.h)(
|
|
1586
2084
|
"ul",
|
|
1587
2085
|
{ style: styles.list },
|
|
1588
2086
|
history.events.value.map(
|
|
1589
|
-
(e) => (0,
|
|
2087
|
+
(e) => (0, import_vue14.h)("li", { key: e.id, style: styles.row }, [
|
|
1590
2088
|
pill(e.outcome === "success" ? "ok" : "bad", e.outcome),
|
|
1591
2089
|
" ",
|
|
1592
|
-
(0,
|
|
2090
|
+
(0, import_vue14.h)("span", { style: styles.muted }, e.method),
|
|
1593
2091
|
" ",
|
|
1594
|
-
(0,
|
|
2092
|
+
(0, import_vue14.h)("span", { style: styles.mono }, e.ipAddress ?? "\u2014"),
|
|
1595
2093
|
" ",
|
|
1596
|
-
(0,
|
|
2094
|
+
(0, import_vue14.h)("span", { style: styles.muted }, (0, import_client.describeLocation)(e.location) ?? ""),
|
|
1597
2095
|
e.location?.proxy || e.location?.hosting ? [" ", pill("warn", e.location.proxy ? "proxy/VPN" : "hosting")] : "",
|
|
1598
2096
|
" ",
|
|
1599
|
-
(0,
|
|
2097
|
+
(0, import_vue14.h)("span", { style: styles.muted }, new Date(e.createdAt).toLocaleString())
|
|
1600
2098
|
])
|
|
1601
2099
|
)
|
|
1602
2100
|
),
|
|
@@ -1608,27 +2106,27 @@ var UsersScreen = (0, import_vue11.defineComponent)({
|
|
|
1608
2106
|
});
|
|
1609
2107
|
|
|
1610
2108
|
// src/screens/CatalogScreen.ts
|
|
1611
|
-
var
|
|
1612
|
-
var
|
|
1613
|
-
var CatalogScreen = (0,
|
|
2109
|
+
var import_vue_admin12 = require("@fonderie/vue-admin");
|
|
2110
|
+
var import_vue15 = require("vue");
|
|
2111
|
+
var CatalogScreen = (0, import_vue15.defineComponent)({
|
|
1614
2112
|
name: "FonderieCatalogScreen",
|
|
1615
2113
|
props: { client: { type: Object, required: true } },
|
|
1616
2114
|
setup(props) {
|
|
1617
|
-
const { catalog, isLoading, error, refresh, deletePlan } = (0,
|
|
2115
|
+
const { catalog, isLoading, error, refresh, deletePlan } = (0, import_vue_admin12.useAdminCatalog)(props.client);
|
|
1618
2116
|
const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
|
|
1619
2117
|
return () => {
|
|
1620
2118
|
const c = catalog.value;
|
|
1621
|
-
return (0,
|
|
2119
|
+
return (0, import_vue15.h)("div", { style: styles.container }, [
|
|
1622
2120
|
pageHeader(
|
|
1623
2121
|
"Catalog",
|
|
1624
2122
|
"What you sell: plans as configured in code, and as stored in the database, side by side.",
|
|
1625
|
-
[
|
|
2123
|
+
[refreshButton2("Refresh", isLoading.value, () => void refresh())]
|
|
1626
2124
|
),
|
|
1627
|
-
error.value ? (0,
|
|
1628
|
-
isLoading.value && !c ? (0,
|
|
2125
|
+
error.value ? (0, import_vue15.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
2126
|
+
isLoading.value && !c ? (0, import_vue15.h)("p", { style: styles.status }, "Loading\u2026") : null,
|
|
1629
2127
|
c ? [
|
|
1630
|
-
(0,
|
|
1631
|
-
(0,
|
|
2128
|
+
(0, import_vue15.h)("h2", { style: { ...styles.subtitle, marginTop: 0 } }, "Configured (code)"),
|
|
2129
|
+
(0, import_vue15.h)(
|
|
1632
2130
|
"pre",
|
|
1633
2131
|
{
|
|
1634
2132
|
style: {
|
|
@@ -1642,7 +2140,7 @@ var CatalogScreen = (0, import_vue12.defineComponent)({
|
|
|
1642
2140
|
},
|
|
1643
2141
|
JSON.stringify(c.configured, null, 2)
|
|
1644
2142
|
),
|
|
1645
|
-
(0,
|
|
2143
|
+
(0, import_vue15.h)("h2", { style: styles.subtitle }, "Stored (database)"),
|
|
1646
2144
|
c.stored.length === 0 ? empty(
|
|
1647
2145
|
"No stored plans",
|
|
1648
2146
|
"Plans are configured in code; nothing has been written to the database.",
|
|
@@ -1650,10 +2148,10 @@ var CatalogScreen = (0, import_vue12.defineComponent)({
|
|
|
1650
2148
|
) : table(
|
|
1651
2149
|
["Plan", "Tier", "Seats", "Monthly", "Yearly", "Trial", ""],
|
|
1652
2150
|
c.stored.map(
|
|
1653
|
-
(p) => (0,
|
|
2151
|
+
(p) => (0, import_vue15.h)("tr", { key: p.id }, [
|
|
1654
2152
|
td([
|
|
1655
|
-
(0,
|
|
1656
|
-
p.description ? (0,
|
|
2153
|
+
(0, import_vue15.h)("strong", p.name),
|
|
2154
|
+
p.description ? (0, import_vue15.h)("div", { style: styles.muted }, p.description) : ""
|
|
1657
2155
|
]),
|
|
1658
2156
|
td(String(p.tier)),
|
|
1659
2157
|
td(p.seats == null ? "\u221E" : String(p.seats)),
|
|
@@ -1661,7 +2159,7 @@ var CatalogScreen = (0, import_vue12.defineComponent)({
|
|
|
1661
2159
|
td(money(p.pricing?.yearly)),
|
|
1662
2160
|
td(p.trialDays ? `${p.trialDays} d` : "\u2014"),
|
|
1663
2161
|
td(
|
|
1664
|
-
(0,
|
|
2162
|
+
(0, import_vue15.h)(
|
|
1665
2163
|
"button",
|
|
1666
2164
|
{
|
|
1667
2165
|
type: "button",
|
|
@@ -1685,293 +2183,205 @@ var CatalogScreen = (0, import_vue12.defineComponent)({
|
|
|
1685
2183
|
});
|
|
1686
2184
|
|
|
1687
2185
|
// src/screens/SubscriberScreen.ts
|
|
1688
|
-
var
|
|
1689
|
-
var
|
|
1690
|
-
var
|
|
2186
|
+
var import_vue_admin13 = require("@fonderie/vue-admin");
|
|
2187
|
+
var import_vue16 = require("vue");
|
|
2188
|
+
var FILTERS = [
|
|
2189
|
+
{ label: "All", match: () => true },
|
|
2190
|
+
{ label: "Active", match: (s) => s === "active" },
|
|
2191
|
+
{ label: "Trialing", match: (s) => s === "trialing" },
|
|
2192
|
+
{ label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
|
|
2193
|
+
{ label: "Canceled", match: (s) => s === "canceled" }
|
|
2194
|
+
];
|
|
2195
|
+
var SubscriberScreen = (0, import_vue16.defineComponent)({
|
|
1691
2196
|
name: "FonderieSubscriberScreen",
|
|
1692
2197
|
props: {
|
|
1693
2198
|
client: { type: Object, required: true },
|
|
1694
|
-
pageSize: { type: Number, default: 50 }
|
|
2199
|
+
pageSize: { type: Number, default: 50 },
|
|
2200
|
+
/**
|
|
2201
|
+
* A user subscriber opens on the Users page, where their plan and credits
|
|
2202
|
+
* live beside their account. Omit to open everything here.
|
|
2203
|
+
*/
|
|
2204
|
+
onOpenUser: { type: Function, default: void 0 }
|
|
1695
2205
|
},
|
|
1696
2206
|
setup(props) {
|
|
1697
|
-
const
|
|
1698
|
-
const
|
|
1699
|
-
const
|
|
1700
|
-
const
|
|
1701
|
-
const
|
|
1702
|
-
const
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
idInput.value = id;
|
|
1706
|
-
subscriber.value = { type: t2, id };
|
|
2207
|
+
const list = (0, import_vue_admin13.useAdminSubscribers)(props.client, { limit: props.pageSize });
|
|
2208
|
+
const filter = (0, import_vue16.ref)("All");
|
|
2209
|
+
const open = (0, import_vue16.ref)(null);
|
|
2210
|
+
const type = (0, import_vue16.ref)("workspace");
|
|
2211
|
+
const idInput = (0, import_vue16.ref)("");
|
|
2212
|
+
const openRow = (t2, id) => {
|
|
2213
|
+
if (t2 === "user" && props.onOpenUser) props.onOpenUser(id);
|
|
2214
|
+
else open.value = { type: t2, id };
|
|
1707
2215
|
};
|
|
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", {
|
|
1712
|
-
value: model.value,
|
|
1713
|
-
onInput: (e) => model.value = e.target.value,
|
|
1714
|
-
placeholder,
|
|
1715
|
-
style: { ...styles.input, ...extra },
|
|
1716
|
-
"aria-label": placeholder
|
|
1717
|
-
});
|
|
1718
2216
|
return () => {
|
|
1719
|
-
const
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
"Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
|
|
1725
|
-
),
|
|
1726
|
-
(0, import_vue13.h)(
|
|
1727
|
-
"form",
|
|
1728
|
-
{
|
|
1729
|
-
style: styles.toolbar,
|
|
1730
|
-
onSubmit: (e) => {
|
|
1731
|
-
e.preventDefault();
|
|
1732
|
-
granted.value = null;
|
|
1733
|
-
subscriber.value = idInput.value.trim() ? { type: type.value, id: idInput.value.trim() } : null;
|
|
1734
|
-
}
|
|
1735
|
-
},
|
|
1736
|
-
[
|
|
1737
|
-
(0, import_vue13.h)(
|
|
1738
|
-
"select",
|
|
1739
|
-
{
|
|
1740
|
-
value: type.value,
|
|
1741
|
-
onChange: (e) => type.value = e.target.value,
|
|
1742
|
-
style: styles.input,
|
|
1743
|
-
"aria-label": "Type"
|
|
1744
|
-
},
|
|
1745
|
-
[
|
|
1746
|
-
(0, import_vue13.h)("option", { value: "user" }, "user"),
|
|
1747
|
-
(0, import_vue13.h)("option", { value: "workspace" }, "workspace")
|
|
1748
|
-
]
|
|
1749
|
-
),
|
|
1750
|
-
input(idInput, "subscriber id", { minWidth: "280px" }),
|
|
1751
|
-
(0, import_vue13.h)(
|
|
2217
|
+
const o = open.value;
|
|
2218
|
+
if (o) {
|
|
2219
|
+
return (0, import_vue16.h)("div", { style: styles.container }, [
|
|
2220
|
+
pageHeader(`${o.type}/${o.id}`, "Plan, credits and what moved.", [
|
|
2221
|
+
(0, import_vue16.h)(
|
|
1752
2222
|
"button",
|
|
1753
|
-
{ type: "
|
|
1754
|
-
[icon("
|
|
1755
|
-
),
|
|
1756
|
-
subscriber.value ? (0, import_vue13.h)(
|
|
1757
|
-
"button",
|
|
1758
|
-
{
|
|
1759
|
-
type: "button",
|
|
1760
|
-
style: styles.button,
|
|
1761
|
-
onClick: () => {
|
|
1762
|
-
subscriber.value = null;
|
|
1763
|
-
idInput.value = "";
|
|
1764
|
-
granted.value = null;
|
|
1765
|
-
}
|
|
1766
|
-
},
|
|
1767
|
-
[icon("back", 14), "All subscribers"]
|
|
1768
|
-
) : (0, import_vue13.h)(
|
|
1769
|
-
"button",
|
|
1770
|
-
{
|
|
1771
|
-
type: "button",
|
|
1772
|
-
style: styles.button,
|
|
1773
|
-
disabled: list.isLoading.value,
|
|
1774
|
-
onClick: () => void list.refresh()
|
|
1775
|
-
},
|
|
1776
|
-
"Refresh"
|
|
2223
|
+
{ type: "button", style: styles.buttonGhost, onClick: () => open.value = null },
|
|
2224
|
+
[icon("back", 14), "All subscriptions"]
|
|
1777
2225
|
)
|
|
1778
|
-
]
|
|
2226
|
+
]),
|
|
2227
|
+
(0, import_vue16.h)(SubscriberBilling, {
|
|
2228
|
+
key: `${o.type}/${o.id}`,
|
|
2229
|
+
client: props.client,
|
|
2230
|
+
subscriberType: o.type,
|
|
2231
|
+
subscriberId: o.id
|
|
2232
|
+
})
|
|
2233
|
+
]);
|
|
2234
|
+
}
|
|
2235
|
+
const match = FILTERS.find((f) => f.label === filter.value)?.match ?? (() => true);
|
|
2236
|
+
const rows = list.subscriptions.value.filter((s) => match(s.status));
|
|
2237
|
+
return (0, import_vue16.h)("div", { style: styles.container }, [
|
|
2238
|
+
pageHeader(
|
|
2239
|
+
"Subscriptions",
|
|
2240
|
+
"Who pays, and who is behind. A user's plan and credits are also on their page under Users \u2014 free users appear only there.",
|
|
2241
|
+
[refreshButton(() => void list.refresh(), list.isLoading.value)]
|
|
1779
2242
|
),
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
sub.currentPeriodEnd ? new Date(sub.currentPeriodEnd).toLocaleDateString() : (0, import_vue13.h)("span", { style: styles.muted }, "\u2014")
|
|
1811
|
-
)
|
|
1812
|
-
])
|
|
1813
|
-
)
|
|
2243
|
+
(0, import_vue16.h)("div", { style: { ...styles.toolbar, justifyContent: "space-between" } }, [
|
|
2244
|
+
(0, import_vue16.h)(
|
|
2245
|
+
"fieldset",
|
|
2246
|
+
{
|
|
2247
|
+
style: {
|
|
2248
|
+
display: "flex",
|
|
2249
|
+
gap: "6px",
|
|
2250
|
+
flexWrap: "wrap",
|
|
2251
|
+
border: "none",
|
|
2252
|
+
margin: 0,
|
|
2253
|
+
padding: 0,
|
|
2254
|
+
minWidth: 0
|
|
2255
|
+
},
|
|
2256
|
+
"aria-label": "Filter by status"
|
|
2257
|
+
},
|
|
2258
|
+
FILTERS.map((f) => {
|
|
2259
|
+
const n = list.subscriptions.value.filter((s) => f.match(s.status)).length;
|
|
2260
|
+
const on = filter.value === f.label;
|
|
2261
|
+
return (0, import_vue16.h)(
|
|
2262
|
+
"button",
|
|
2263
|
+
{
|
|
2264
|
+
key: f.label,
|
|
2265
|
+
type: "button",
|
|
2266
|
+
"aria-pressed": on,
|
|
2267
|
+
onClick: () => filter.value = f.label,
|
|
2268
|
+
style: { ...on ? styles.buttonPrimary : styles.button, height: "28px" }
|
|
2269
|
+
},
|
|
2270
|
+
[f.label, (0, import_vue16.h)("span", { style: { opacity: 0.7 } }, String(n))]
|
|
2271
|
+
);
|
|
2272
|
+
})
|
|
1814
2273
|
),
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
"button",
|
|
2274
|
+
(0, import_vue16.h)(
|
|
2275
|
+
"form",
|
|
1818
2276
|
{
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
2277
|
+
style: { display: "flex", gap: "6px" },
|
|
2278
|
+
onSubmit: (e) => {
|
|
2279
|
+
e.preventDefault();
|
|
2280
|
+
if (idInput.value.trim()) openRow(type.value, idInput.value.trim());
|
|
2281
|
+
}
|
|
1822
2282
|
},
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
|
|
1865
|
-
}
|
|
1866
|
-
},
|
|
1867
|
-
w.balance
|
|
1868
|
-
),
|
|
1869
|
-
` ${w.currency} `,
|
|
1870
|
-
(0, import_vue13.h)(
|
|
1871
|
-
"span",
|
|
1872
|
-
{ style: styles.muted },
|
|
1873
|
-
`(granted ${w.granted ?? "0"} \xB7 purchased ${w.purchased ?? "0"}) \xB7 minor units, precision ${w.precision}`
|
|
1874
|
-
)
|
|
1875
|
-
]
|
|
1876
|
-
),
|
|
1877
|
-
(0, import_vue13.h)(
|
|
1878
|
-
"form",
|
|
1879
|
-
{
|
|
1880
|
-
style: styles.toolbar,
|
|
1881
|
-
onSubmit: (e) => {
|
|
1882
|
-
e.preventDefault();
|
|
1883
|
-
const sub = subscriber.value;
|
|
1884
|
-
if (!sub) return;
|
|
1885
|
-
const key = `admin-grant-${sub.type}-${sub.id}-${Date.now()}`;
|
|
1886
|
-
const amt = amount.value.trim();
|
|
1887
|
-
void grant({
|
|
1888
|
-
amount: amt,
|
|
1889
|
-
...note.value ? { description: note.value } : {},
|
|
1890
|
-
idempotencyKey: key
|
|
1891
|
-
}).then(() => {
|
|
1892
|
-
granted.value = `Granted ${amt} ${w.currency}.`;
|
|
1893
|
-
amount.value = "";
|
|
1894
|
-
note.value = "";
|
|
1895
|
-
}).catch(() => {
|
|
1896
|
-
});
|
|
1897
|
-
}
|
|
1898
|
-
},
|
|
1899
|
-
[
|
|
1900
|
-
input(amount, "amount (minor units)"),
|
|
1901
|
-
input(note, "reason (optional)", { minWidth: "220px" }),
|
|
1902
|
-
(0, import_vue13.h)(
|
|
2283
|
+
[
|
|
2284
|
+
(0, import_vue16.h)(
|
|
2285
|
+
"select",
|
|
2286
|
+
{
|
|
2287
|
+
value: type.value,
|
|
2288
|
+
onChange: (e) => type.value = e.target.value,
|
|
2289
|
+
style: styles.input,
|
|
2290
|
+
"aria-label": "Type"
|
|
2291
|
+
},
|
|
2292
|
+
[
|
|
2293
|
+
(0, import_vue16.h)("option", { value: "workspace" }, "workspace"),
|
|
2294
|
+
(0, import_vue16.h)("option", { value: "user" }, "user")
|
|
2295
|
+
]
|
|
2296
|
+
),
|
|
2297
|
+
(0, import_vue16.h)("input", {
|
|
2298
|
+
value: idInput.value,
|
|
2299
|
+
onInput: (e) => idInput.value = e.target.value,
|
|
2300
|
+
placeholder: "subscriber id",
|
|
2301
|
+
style: { ...styles.input, width: "220px" },
|
|
2302
|
+
"aria-label": "Subscriber id"
|
|
2303
|
+
}),
|
|
2304
|
+
(0, import_vue16.h)(
|
|
2305
|
+
"button",
|
|
2306
|
+
{ type: "submit", style: styles.button, disabled: !idInput.value.trim() },
|
|
2307
|
+
[icon("search", 14), "Open"]
|
|
2308
|
+
)
|
|
2309
|
+
]
|
|
2310
|
+
)
|
|
2311
|
+
]),
|
|
2312
|
+
list.error.value ? (0, import_vue16.h)("p", { style: styles.error, role: "alert" }, list.error.value.explanation) : null,
|
|
2313
|
+
rows.length === 0 && !list.isLoading.value ? empty(
|
|
2314
|
+
filter.value === "All" ? "No subscriptions yet" : `No ${filter.value.toLowerCase()} subscriptions`,
|
|
2315
|
+
"Subscriptions appear here once someone checks out.",
|
|
2316
|
+
"subscriber"
|
|
2317
|
+
) : table(
|
|
2318
|
+
["Subscriber", "Plan", "Status", "Renews / ends"],
|
|
2319
|
+
rows.map((s) => {
|
|
2320
|
+
const end = periodEnd(s);
|
|
2321
|
+
return (0, import_vue16.h)("tr", { key: s.id }, [
|
|
2322
|
+
td(
|
|
2323
|
+
(0, import_vue16.h)(
|
|
1903
2324
|
"button",
|
|
1904
2325
|
{
|
|
1905
|
-
type: "
|
|
1906
|
-
style: styles.
|
|
1907
|
-
|
|
2326
|
+
type: "button",
|
|
2327
|
+
style: { ...styles.link, ...styles.mono },
|
|
2328
|
+
onClick: () => openRow(s.subscriberType, s.subscriberId)
|
|
1908
2329
|
},
|
|
1909
|
-
|
|
1910
|
-
)
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
(
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2330
|
+
`${s.subscriberType}/${s.subscriberId}`
|
|
2331
|
+
)
|
|
2332
|
+
),
|
|
2333
|
+
td([
|
|
2334
|
+
(0, import_vue16.h)("strong", s.plan),
|
|
2335
|
+
" ",
|
|
2336
|
+
(0, import_vue16.h)("span", { style: styles.muted }, `\xB7 ${s.interval}`)
|
|
2337
|
+
]),
|
|
2338
|
+
td([
|
|
2339
|
+
pill(statusTone(s.status), s.status),
|
|
2340
|
+
s.cancelAtPeriodEnd && s.status !== "canceled" ? (0, import_vue16.h)("span", [" ", pill("warn", "cancels", false)]) : null
|
|
2341
|
+
]),
|
|
2342
|
+
td(
|
|
2343
|
+
end ? [(0, import_vue16.h)("span", { style: styles.muted }, end.label), ` ${end.date}`] : (0, import_vue16.h)("span", { style: styles.muted }, "\u2014"),
|
|
2344
|
+
{ whiteSpace: "nowrap" }
|
|
1924
2345
|
)
|
|
1925
|
-
)
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
style: { ...styles.button, marginTop: "8px" },
|
|
1931
|
-
onClick: () => void loadMoreLedger()
|
|
1932
|
-
},
|
|
1933
|
-
"Load more"
|
|
1934
|
-
) : null
|
|
1935
|
-
] : (0, import_vue13.h)(
|
|
1936
|
-
"p",
|
|
1937
|
-
{ style: styles.muted },
|
|
1938
|
-
"No wallet \u2014 billing has no wallet configuration."
|
|
1939
|
-
)
|
|
1940
|
-
] : null
|
|
2346
|
+
]);
|
|
2347
|
+
})
|
|
2348
|
+
),
|
|
2349
|
+
list.isLoading.value ? (0, import_vue16.h)("p", { style: styles.status }, "Loading\u2026") : null,
|
|
2350
|
+
list.hasMore.value && !list.isLoading.value ? loadMoreButton(() => void list.loadMore()) : null
|
|
1941
2351
|
]);
|
|
1942
2352
|
};
|
|
1943
2353
|
}
|
|
1944
2354
|
});
|
|
1945
2355
|
|
|
1946
2356
|
// src/screens/AuditScreen.ts
|
|
1947
|
-
var
|
|
1948
|
-
var
|
|
1949
|
-
var AuditScreen = (0,
|
|
2357
|
+
var import_vue_admin14 = require("@fonderie/vue-admin");
|
|
2358
|
+
var import_vue17 = require("vue");
|
|
2359
|
+
var AuditScreen = (0, import_vue17.defineComponent)({
|
|
1950
2360
|
name: "FonderieAuditScreen",
|
|
1951
2361
|
props: {
|
|
1952
2362
|
client: { type: Object, required: true },
|
|
1953
2363
|
pageSize: { type: Number, default: 50 }
|
|
1954
2364
|
},
|
|
1955
2365
|
setup(props) {
|
|
1956
|
-
const draft = (0,
|
|
1957
|
-
const query = (0,
|
|
1958
|
-
const { events, hasMore, isLoading, error, refresh, loadMore } = (0,
|
|
2366
|
+
const draft = (0, import_vue17.ref)({ workspaceId: "", type: "", actorId: "" });
|
|
2367
|
+
const query = (0, import_vue17.ref)({ limit: props.pageSize });
|
|
2368
|
+
const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_vue_admin14.useAdminAudit)(
|
|
1959
2369
|
props.client,
|
|
1960
2370
|
query
|
|
1961
2371
|
);
|
|
1962
|
-
const field = (key, placeholder) => (0,
|
|
2372
|
+
const field = (key, placeholder) => (0, import_vue17.h)("input", {
|
|
1963
2373
|
value: draft.value[key],
|
|
1964
2374
|
onInput: (e) => draft.value = { ...draft.value, [key]: e.target.value },
|
|
1965
2375
|
placeholder,
|
|
1966
2376
|
style: styles.input,
|
|
1967
2377
|
"aria-label": placeholder
|
|
1968
2378
|
});
|
|
1969
|
-
return () => (0,
|
|
2379
|
+
return () => (0, import_vue17.h)("div", { style: styles.container }, [
|
|
1970
2380
|
pageHeader(
|
|
1971
2381
|
"Audit",
|
|
1972
2382
|
"What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
|
|
1973
2383
|
),
|
|
1974
|
-
(0,
|
|
2384
|
+
(0, import_vue17.h)(
|
|
1975
2385
|
"form",
|
|
1976
2386
|
{
|
|
1977
2387
|
style: styles.toolbar,
|
|
@@ -1990,19 +2400,19 @@ var AuditScreen = (0, import_vue14.defineComponent)({
|
|
|
1990
2400
|
field("workspaceId", "workspace id (all if empty)"),
|
|
1991
2401
|
field("type", "event type"),
|
|
1992
2402
|
field("actorId", "actor id"),
|
|
1993
|
-
(0,
|
|
2403
|
+
(0, import_vue17.h)(
|
|
1994
2404
|
"button",
|
|
1995
2405
|
{ type: "submit", style: styles.buttonPrimary, disabled: isLoading.value },
|
|
1996
2406
|
[icon("search", 14), "Filter"]
|
|
1997
2407
|
),
|
|
1998
|
-
|
|
2408
|
+
refreshButton2("Refresh", isLoading.value, () => void refresh())
|
|
1999
2409
|
]
|
|
2000
2410
|
),
|
|
2001
|
-
error.value ? (0,
|
|
2411
|
+
error.value ? (0, import_vue17.h)("p", { style: styles.error, role: "alert" }, error.value.explanation) : null,
|
|
2002
2412
|
events.value.length === 0 && !isLoading.value ? empty("Nothing recorded for this filter", void 0, "audit") : table(
|
|
2003
2413
|
["When", "Type", "Workspace", "Actor", "Request"],
|
|
2004
2414
|
events.value.map(
|
|
2005
|
-
(e) => (0,
|
|
2415
|
+
(e) => (0, import_vue17.h)("tr", { key: e.id }, [
|
|
2006
2416
|
td(new Date(e.createdAt).toLocaleString(), styles.muted),
|
|
2007
2417
|
td(e.type, styles.mono),
|
|
2008
2418
|
td(String(e.payload["workspaceId"] ?? "\u2014"), styles.mono),
|
|
@@ -2011,7 +2421,7 @@ var AuditScreen = (0, import_vue14.defineComponent)({
|
|
|
2011
2421
|
])
|
|
2012
2422
|
)
|
|
2013
2423
|
),
|
|
2014
|
-
isLoading.value ? (0,
|
|
2424
|
+
isLoading.value ? (0, import_vue17.h)("p", { style: styles.status }, "Loading\u2026") : null,
|
|
2015
2425
|
hasMore.value && !isLoading.value ? loadMoreButton(() => void loadMore()) : null
|
|
2016
2426
|
]);
|
|
2017
2427
|
}
|
|
@@ -2034,7 +2444,7 @@ var NAV = [
|
|
|
2034
2444
|
group: "Money",
|
|
2035
2445
|
items: [
|
|
2036
2446
|
{ page: "catalog", label: "Catalog", needs: "billing" },
|
|
2037
|
-
{ page: "subscriber", label: "
|
|
2447
|
+
{ page: "subscriber", label: "Subscriptions", needs: "billing" }
|
|
2038
2448
|
]
|
|
2039
2449
|
},
|
|
2040
2450
|
{ group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
|
|
@@ -2044,7 +2454,8 @@ var NAV = [
|
|
|
2044
2454
|
items: [
|
|
2045
2455
|
{ page: "audit", label: "Audit", needs: "audit" },
|
|
2046
2456
|
{ page: "log", label: "Admin log" },
|
|
2047
|
-
{ page: "
|
|
2457
|
+
{ page: "operators", label: "Operators", needs: "operators" },
|
|
2458
|
+
{ page: "tokens", label: "Tokens" },
|
|
2048
2459
|
{ page: "migrations", label: "Migrations" }
|
|
2049
2460
|
]
|
|
2050
2461
|
}
|
|
@@ -2063,10 +2474,11 @@ var ICON = {
|
|
|
2063
2474
|
audit: "audit",
|
|
2064
2475
|
log: "log",
|
|
2065
2476
|
tokens: "tokens",
|
|
2477
|
+
operators: "users",
|
|
2066
2478
|
migrations: "migrations"
|
|
2067
2479
|
};
|
|
2068
2480
|
var NARROW = "(max-width: 820px)";
|
|
2069
|
-
var AdminShell = (0,
|
|
2481
|
+
var AdminShell = (0, import_vue18.defineComponent)({
|
|
2070
2482
|
name: "FonderieAdminShell",
|
|
2071
2483
|
props: {
|
|
2072
2484
|
client: { type: Object, required: true },
|
|
@@ -2088,62 +2500,86 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2088
2500
|
appName: { type: String, default: "Admin" },
|
|
2089
2501
|
// A badge beside the name — the deployment's environment ("production").
|
|
2090
2502
|
// Production is tinted so an operator always knows where their clicks land.
|
|
2091
|
-
envLabel: { type: String, default: void 0 }
|
|
2503
|
+
envLabel: { type: String, default: void 0 },
|
|
2504
|
+
// The deployment has operator accounts (@fonderie/admin with a store):
|
|
2505
|
+
// shows the Operators page.
|
|
2506
|
+
operators: { type: Boolean, default: false },
|
|
2507
|
+
// The signed-in operator's email, when a person (not a token) is signed in.
|
|
2508
|
+
currentOperator: { type: String, default: void 0 }
|
|
2092
2509
|
},
|
|
2093
2510
|
// `footer` slot: pinned to the bottom of the sidebar — session controls
|
|
2094
2511
|
// (theme, sign out). The React shell takes the same thing as a `footer` prop.
|
|
2095
2512
|
emits: { navigate: (_page) => true },
|
|
2096
2513
|
setup(props, { emit, slots }) {
|
|
2097
2514
|
const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
|
|
2098
|
-
const narrow = (0,
|
|
2099
|
-
const drawer = (0,
|
|
2515
|
+
const narrow = (0, import_vue18.ref)(query?.matches ?? false);
|
|
2516
|
+
const drawer = (0, import_vue18.ref)(false);
|
|
2100
2517
|
const onMedia = (e) => narrow.value = e.matches;
|
|
2101
|
-
(0,
|
|
2102
|
-
(0,
|
|
2103
|
-
const own = (0,
|
|
2104
|
-
const current = (0,
|
|
2105
|
-
const editing = (0,
|
|
2518
|
+
(0, import_vue18.onMounted)(() => query?.addEventListener("change", onMedia));
|
|
2519
|
+
(0, import_vue18.onBeforeUnmount)(() => query?.removeEventListener("change", onMedia));
|
|
2520
|
+
const own = (0, import_vue18.ref)("attention");
|
|
2521
|
+
const current = (0, import_vue18.computed)(() => props.page ?? own.value);
|
|
2522
|
+
const editing = (0, import_vue18.ref)(null);
|
|
2523
|
+
const openUser = (0, import_vue18.ref)(void 0);
|
|
2106
2524
|
const go = (p) => {
|
|
2107
2525
|
editing.value = null;
|
|
2108
2526
|
emit("navigate", p);
|
|
2109
2527
|
if (props.page === void 0) own.value = p;
|
|
2110
2528
|
drawer.value = false;
|
|
2111
2529
|
};
|
|
2112
|
-
const back = (label) => (0,
|
|
2113
|
-
(0,
|
|
2530
|
+
const back = (label) => (0, import_vue18.h)("div", { style: { padding: "24px 40px 0" } }, [
|
|
2531
|
+
(0, import_vue18.h)(
|
|
2114
2532
|
"button",
|
|
2115
2533
|
{ type: "button", style: styles.buttonGhost, onClick: () => editing.value = null },
|
|
2116
2534
|
[icon("back", 14), label]
|
|
2117
2535
|
)
|
|
2118
2536
|
]);
|
|
2119
|
-
const missing = (text) => (0,
|
|
2537
|
+
const missing = (text) => (0, import_vue18.h)("p", { style: { ...styles.status, padding: "32px 40px" } }, text);
|
|
2120
2538
|
const body = () => {
|
|
2121
2539
|
const c = props.client;
|
|
2122
2540
|
switch (current.value) {
|
|
2123
2541
|
case "attention":
|
|
2124
|
-
return (0,
|
|
2542
|
+
return (0, import_vue18.h)(AttentionScreen, { client: c });
|
|
2125
2543
|
case "modules":
|
|
2126
|
-
return (0,
|
|
2544
|
+
return (0, import_vue18.h)(ModulesScreen, { client: c });
|
|
2127
2545
|
case "doctor":
|
|
2128
|
-
return (0,
|
|
2546
|
+
return (0, import_vue18.h)(DoctorScreen, { client: c });
|
|
2129
2547
|
case "environment":
|
|
2130
|
-
return (0,
|
|
2548
|
+
return (0, import_vue18.h)(EnvironmentScreen, { client: c });
|
|
2131
2549
|
case "routes":
|
|
2132
|
-
return (0,
|
|
2550
|
+
return (0, import_vue18.h)(RoutesScreen, { client: c });
|
|
2133
2551
|
case "tokens":
|
|
2134
|
-
return (0,
|
|
2552
|
+
return (0, import_vue18.h)(TokensScreen, { client: c });
|
|
2553
|
+
case "operators":
|
|
2554
|
+
return (0, import_vue18.h)(OperatorsScreen, {
|
|
2555
|
+
client: c,
|
|
2556
|
+
...props.currentOperator ? { me: props.currentOperator } : {}
|
|
2557
|
+
});
|
|
2135
2558
|
case "migrations":
|
|
2136
|
-
return (0,
|
|
2559
|
+
return (0, import_vue18.h)(MigrationsScreen, { client: c });
|
|
2137
2560
|
case "log":
|
|
2138
|
-
return (0,
|
|
2561
|
+
return (0, import_vue18.h)(AdminLogScreen, { client: c });
|
|
2139
2562
|
case "catalog":
|
|
2140
|
-
return props.billingClient ? (0,
|
|
2563
|
+
return props.billingClient ? (0, import_vue18.h)(CatalogScreen, { client: props.billingClient }) : missing("Pass a BillingAdminClient to see the catalog here.");
|
|
2141
2564
|
case "subscriber":
|
|
2142
|
-
return props.billingClient ? (0,
|
|
2565
|
+
return props.billingClient ? (0, import_vue18.h)(SubscriberScreen, {
|
|
2566
|
+
client: props.billingClient,
|
|
2567
|
+
...props.authClient ? {
|
|
2568
|
+
onOpenUser: (id) => {
|
|
2569
|
+
openUser.value = id;
|
|
2570
|
+
go("users");
|
|
2571
|
+
}
|
|
2572
|
+
} : {}
|
|
2573
|
+
}) : missing("Pass a BillingAdminClient to look up subscribers here.");
|
|
2143
2574
|
case "audit":
|
|
2144
|
-
return props.auditClient ? (0,
|
|
2575
|
+
return props.auditClient ? (0, import_vue18.h)(AuditScreen, { client: props.auditClient }) : missing("Pass an AuditAdminClient to see the audit trail here.");
|
|
2145
2576
|
case "users":
|
|
2146
|
-
return props.authClient ? (0,
|
|
2577
|
+
return props.authClient ? (0, import_vue18.h)(UsersScreen, {
|
|
2578
|
+
key: openUser.value ?? "list",
|
|
2579
|
+
client: props.authClient,
|
|
2580
|
+
...props.billingClient ? { billingClient: props.billingClient } : {},
|
|
2581
|
+
...openUser.value ? { openUserId: openUser.value } : {}
|
|
2582
|
+
}) : missing("Pass an AuthAdminClient to look up users here.");
|
|
2147
2583
|
case "settings": {
|
|
2148
2584
|
const cc = props.configClient;
|
|
2149
2585
|
if (!cc) return missing("Pass a ConfigAdminClient to manage config and secrets here.");
|
|
@@ -2151,7 +2587,7 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2151
2587
|
if (e && e.kind !== "template")
|
|
2152
2588
|
return [
|
|
2153
2589
|
back("Config & secrets"),
|
|
2154
|
-
(0,
|
|
2590
|
+
(0, import_vue18.h)(import_vue_config_admin_screens.ConfigEditorScreen, {
|
|
2155
2591
|
client: cc,
|
|
2156
2592
|
kind: e.kind,
|
|
2157
2593
|
configKey: e.key,
|
|
@@ -2159,7 +2595,7 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2159
2595
|
onSaved: () => editing.value = null
|
|
2160
2596
|
})
|
|
2161
2597
|
];
|
|
2162
|
-
return (0,
|
|
2598
|
+
return (0, import_vue18.h)(import_vue_config_admin_screens.ConfigListScreen, {
|
|
2163
2599
|
client: cc,
|
|
2164
2600
|
...props.environment !== void 0 ? { environment: props.environment } : {},
|
|
2165
2601
|
"onSelect-config": (key) => editing.value = { kind: "config", key },
|
|
@@ -2176,27 +2612,27 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2176
2612
|
if (e?.kind === "template")
|
|
2177
2613
|
return [
|
|
2178
2614
|
back("Templates"),
|
|
2179
|
-
(0,
|
|
2615
|
+
(0, import_vue18.h)(import_vue_courier_admin_screens.TemplateEditorScreen, {
|
|
2180
2616
|
client: kc,
|
|
2181
2617
|
type: e.type,
|
|
2182
2618
|
onSaved: () => editing.value = null
|
|
2183
2619
|
})
|
|
2184
2620
|
];
|
|
2185
|
-
return (0,
|
|
2621
|
+
return (0, import_vue18.h)(import_vue_courier_admin_screens.TemplateListScreen, {
|
|
2186
2622
|
client: kc,
|
|
2187
2623
|
"onSelect-template": (t2) => editing.value = { kind: "template", type: t2.type }
|
|
2188
2624
|
});
|
|
2189
2625
|
}
|
|
2190
2626
|
}
|
|
2191
2627
|
};
|
|
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);
|
|
2628
|
+
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
2629
|
const brand = () => {
|
|
2194
2630
|
const production = props.envLabel ? /^prod/i.test(props.envLabel) : false;
|
|
2195
|
-
return (0,
|
|
2196
|
-
(0,
|
|
2197
|
-
(0,
|
|
2198
|
-
(0,
|
|
2199
|
-
props.envLabel ? (0,
|
|
2631
|
+
return (0, import_vue18.h)("div", { style: styles.navBrand }, [
|
|
2632
|
+
(0, import_vue18.h)("div", { style: styles.navMark }, [icon("mark", 15)]),
|
|
2633
|
+
(0, import_vue18.h)("div", { style: { minWidth: 0, flex: 1 } }, [
|
|
2634
|
+
(0, import_vue18.h)("div", { style: styles.navBrandTitle }, props.appName),
|
|
2635
|
+
props.envLabel ? (0, import_vue18.h)(
|
|
2200
2636
|
"span",
|
|
2201
2637
|
{
|
|
2202
2638
|
style: {
|
|
@@ -2213,7 +2649,7 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2213
2649
|
props.envLabel
|
|
2214
2650
|
) : null
|
|
2215
2651
|
]),
|
|
2216
|
-
narrow.value ? (0,
|
|
2652
|
+
narrow.value ? (0, import_vue18.h)(
|
|
2217
2653
|
"button",
|
|
2218
2654
|
{
|
|
2219
2655
|
type: "button",
|
|
@@ -2225,7 +2661,7 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2225
2661
|
) : null
|
|
2226
2662
|
]);
|
|
2227
2663
|
};
|
|
2228
|
-
const nav = () => (0,
|
|
2664
|
+
const nav = () => (0, import_vue18.h)(
|
|
2229
2665
|
"nav",
|
|
2230
2666
|
{
|
|
2231
2667
|
style: {
|
|
@@ -2236,17 +2672,17 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2236
2672
|
},
|
|
2237
2673
|
[
|
|
2238
2674
|
brand(),
|
|
2239
|
-
(0,
|
|
2675
|
+
(0, import_vue18.h)(
|
|
2240
2676
|
"div",
|
|
2241
2677
|
{ style: styles.navScroll },
|
|
2242
2678
|
NAV.map((g) => {
|
|
2243
2679
|
const items = g.items.filter(visible);
|
|
2244
2680
|
if (items.length === 0) return null;
|
|
2245
|
-
return (0,
|
|
2246
|
-
(0,
|
|
2681
|
+
return (0, import_vue18.h)("div", { key: g.group }, [
|
|
2682
|
+
(0, import_vue18.h)("div", { style: styles.navGroup }, g.group),
|
|
2247
2683
|
...items.map((i) => {
|
|
2248
2684
|
const active = current.value === i.page;
|
|
2249
|
-
return (0,
|
|
2685
|
+
return (0, import_vue18.h)(
|
|
2250
2686
|
"button",
|
|
2251
2687
|
{
|
|
2252
2688
|
key: i.page,
|
|
@@ -2254,7 +2690,10 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2254
2690
|
class: "fonderie-admin-nav-item",
|
|
2255
2691
|
style: { ...styles.navItem, ...active ? styles.navItemActive : {} },
|
|
2256
2692
|
"aria-current": active ? "page" : void 0,
|
|
2257
|
-
onClick: () =>
|
|
2693
|
+
onClick: () => {
|
|
2694
|
+
openUser.value = void 0;
|
|
2695
|
+
go(i.page);
|
|
2696
|
+
}
|
|
2258
2697
|
},
|
|
2259
2698
|
[icon(ICON[i.page], 16, active ? styles.navIconActive : void 0), i.label]
|
|
2260
2699
|
);
|
|
@@ -2262,18 +2701,18 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2262
2701
|
]);
|
|
2263
2702
|
})
|
|
2264
2703
|
),
|
|
2265
|
-
slots["footer"] ? (0,
|
|
2704
|
+
slots["footer"] ? (0, import_vue18.h)("div", { style: styles.navFooter }, slots["footer"]()) : null
|
|
2266
2705
|
]
|
|
2267
2706
|
);
|
|
2268
2707
|
return () => {
|
|
2269
2708
|
const title = NAV.flatMap((g) => g.items).find((i) => i.page === current.value)?.label ?? "";
|
|
2270
|
-
return (0,
|
|
2709
|
+
return (0, import_vue18.h)(
|
|
2271
2710
|
"div",
|
|
2272
2711
|
{ style: { ...styles.shell, ...narrow.value ? { flexDirection: "column" } : {} } },
|
|
2273
2712
|
[
|
|
2274
2713
|
...narrow.value ? [
|
|
2275
|
-
(0,
|
|
2276
|
-
(0,
|
|
2714
|
+
(0, import_vue18.h)("div", { style: styles.topbar }, [
|
|
2715
|
+
(0, import_vue18.h)(
|
|
2277
2716
|
"button",
|
|
2278
2717
|
{
|
|
2279
2718
|
type: "button",
|
|
@@ -2284,19 +2723,19 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2284
2723
|
},
|
|
2285
2724
|
[icon("menu")]
|
|
2286
2725
|
),
|
|
2287
|
-
(0,
|
|
2288
|
-
props.envLabel ? (0,
|
|
2726
|
+
(0, import_vue18.h)("span", { style: { fontWeight: 600, fontSize: "14px" } }, title),
|
|
2727
|
+
props.envLabel ? (0, import_vue18.h)("span", { style: { ...styles.badge, marginLeft: "auto" } }, props.envLabel) : null
|
|
2289
2728
|
]),
|
|
2290
|
-
drawer.value ? (0,
|
|
2729
|
+
drawer.value ? (0, import_vue18.h)("div", { style: styles.drawer }, [
|
|
2291
2730
|
nav(),
|
|
2292
|
-
(0,
|
|
2731
|
+
(0, import_vue18.h)("div", {
|
|
2293
2732
|
style: styles.scrim,
|
|
2294
2733
|
"aria-hidden": "true",
|
|
2295
2734
|
onClick: () => drawer.value = false
|
|
2296
2735
|
})
|
|
2297
2736
|
]) : null
|
|
2298
2737
|
] : [nav()],
|
|
2299
|
-
(0,
|
|
2738
|
+
(0, import_vue18.h)("main", { class: "fonderie-admin-main", style: styles.main }, body())
|
|
2300
2739
|
]
|
|
2301
2740
|
);
|
|
2302
2741
|
};
|
|
@@ -2313,7 +2752,9 @@ var AdminShell = (0, import_vue15.defineComponent)({
|
|
|
2313
2752
|
EnvironmentScreen,
|
|
2314
2753
|
MigrationsScreen,
|
|
2315
2754
|
ModulesScreen,
|
|
2755
|
+
OperatorsScreen,
|
|
2316
2756
|
RoutesScreen,
|
|
2757
|
+
SubscriberBilling,
|
|
2317
2758
|
SubscriberScreen,
|
|
2318
2759
|
TokensScreen,
|
|
2319
2760
|
UsersScreen
|