@crvouga/mockingbird-service-formbricks 1.4.0 → 1.5.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/CHANGELOG.md +10 -0
- package/dist/{chunk-GPMHAIYZ.js → chunk-TNGXNIQB.js} +1410 -606
- package/dist/chunk-TNGXNIQB.js.map +7 -0
- package/dist/{chunk-WLFMWQSH.js → chunk-ZN5CKPD3.js} +2 -2
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/dist/server.js +2 -2
- package/package.json +3 -3
- package/dist/chunk-GPMHAIYZ.js.map +0 -7
- /package/dist/{chunk-WLFMWQSH.js.map → chunk-ZN5CKPD3.js.map} +0 -0
|
@@ -533,10 +533,10 @@ var OpenAPIReferenceError = class extends Error {
|
|
|
533
533
|
};
|
|
534
534
|
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
535
535
|
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
536
|
-
var resolveRef = (
|
|
536
|
+
var resolveRef = (document3, ref) => {
|
|
537
537
|
if (!ref.startsWith("#/"))
|
|
538
538
|
throw new OpenAPIReferenceError(ref);
|
|
539
|
-
let cursor =
|
|
539
|
+
let cursor = document3;
|
|
540
540
|
for (const raw of ref.slice(2).split("/")) {
|
|
541
541
|
const segment = unescapePointer(raw);
|
|
542
542
|
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
@@ -548,14 +548,14 @@ var resolveRef = (document2, ref) => {
|
|
|
548
548
|
throw new OpenAPIReferenceError(ref);
|
|
549
549
|
return cursor;
|
|
550
550
|
};
|
|
551
|
-
var deref = (
|
|
551
|
+
var deref = (document3, value) => {
|
|
552
552
|
let current = value;
|
|
553
553
|
const seen = /* @__PURE__ */ new Set();
|
|
554
554
|
while (isReference(current)) {
|
|
555
555
|
if (seen.has(current.$ref))
|
|
556
556
|
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
557
557
|
seen.add(current.$ref);
|
|
558
|
-
current = resolveRef(
|
|
558
|
+
current = resolveRef(document3, current.$ref);
|
|
559
559
|
}
|
|
560
560
|
return current;
|
|
561
561
|
};
|
|
@@ -573,36 +573,36 @@ var HTTP_METHODS = [
|
|
|
573
573
|
];
|
|
574
574
|
|
|
575
575
|
// ../../openapi/core/dist/document.js
|
|
576
|
-
var mergeParameters = (
|
|
576
|
+
var mergeParameters = (document3, item, own) => {
|
|
577
577
|
const merged = /* @__PURE__ */ new Map();
|
|
578
578
|
for (const raw of item.parameters ?? []) {
|
|
579
|
-
const parameter = deref(
|
|
579
|
+
const parameter = deref(document3, raw);
|
|
580
580
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
581
581
|
}
|
|
582
582
|
for (const raw of own ?? []) {
|
|
583
|
-
const parameter = deref(
|
|
583
|
+
const parameter = deref(document3, raw);
|
|
584
584
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
585
585
|
}
|
|
586
586
|
return [...merged.values()];
|
|
587
587
|
};
|
|
588
|
-
var listOperations = (
|
|
588
|
+
var listOperations = (document3) => {
|
|
589
589
|
const operations = [];
|
|
590
|
-
for (const [path, item] of Object.entries(
|
|
590
|
+
for (const [path, item] of Object.entries(document3.paths)) {
|
|
591
591
|
for (const method of HTTP_METHODS) {
|
|
592
592
|
const operation = item[method];
|
|
593
593
|
if (operation?.operationId === void 0)
|
|
594
594
|
continue;
|
|
595
595
|
const responses = {};
|
|
596
596
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
597
|
-
responses[status] = deref(
|
|
597
|
+
responses[status] = deref(document3, response);
|
|
598
598
|
}
|
|
599
599
|
operations.push({
|
|
600
600
|
operationId: operation.operationId,
|
|
601
601
|
method,
|
|
602
602
|
path,
|
|
603
603
|
operation,
|
|
604
|
-
parameters: mergeParameters(
|
|
605
|
-
requestBody: operation.requestBody === void 0 ? void 0 : deref(
|
|
604
|
+
parameters: mergeParameters(document3, item, operation.parameters),
|
|
605
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
|
|
606
606
|
responses
|
|
607
607
|
});
|
|
608
608
|
}
|
|
@@ -1138,6 +1138,1232 @@ var clearNamespace = (sqlite, namespace) => {
|
|
|
1138
1138
|
});
|
|
1139
1139
|
};
|
|
1140
1140
|
|
|
1141
|
+
// ../../ui/dist/reset.js
|
|
1142
|
+
var CSS_RESET = `*,
|
|
1143
|
+
*::before,
|
|
1144
|
+
*::after {
|
|
1145
|
+
box-sizing: border-box;
|
|
1146
|
+
margin: 0;
|
|
1147
|
+
padding: 0;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
* {
|
|
1151
|
+
scrollbar-width: thin;
|
|
1152
|
+
scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
*::-webkit-scrollbar {
|
|
1156
|
+
width: 12px;
|
|
1157
|
+
height: 12px;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
*::-webkit-scrollbar-track,
|
|
1161
|
+
*::-webkit-scrollbar-corner {
|
|
1162
|
+
background: Canvas;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
*::-webkit-scrollbar-thumb {
|
|
1166
|
+
background: color-mix(in srgb, CanvasText 35%, Canvas);
|
|
1167
|
+
border: 3px solid Canvas;
|
|
1168
|
+
border-radius: 999px;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
html {
|
|
1172
|
+
hanging-punctuation: first allow-end last;
|
|
1173
|
+
-webkit-text-size-adjust: none;
|
|
1174
|
+
text-size-adjust: none;
|
|
1175
|
+
tab-size: 4;
|
|
1176
|
+
font-family:
|
|
1177
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
|
|
1178
|
+
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1179
|
+
font-size: 16px;
|
|
1180
|
+
line-height: 1.5;
|
|
1181
|
+
-webkit-font-smoothing: antialiased;
|
|
1182
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1183
|
+
text-rendering: optimizeLegibility;
|
|
1184
|
+
-webkit-tap-highlight-color: transparent;
|
|
1185
|
+
scroll-behavior: smooth;
|
|
1186
|
+
background: Canvas;
|
|
1187
|
+
color: CanvasText;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
body {
|
|
1191
|
+
min-height: 100svh;
|
|
1192
|
+
line-height: 1.5;
|
|
1193
|
+
background: Canvas;
|
|
1194
|
+
color: CanvasText;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
h1 {
|
|
1198
|
+
font-size: 2em;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
h2 {
|
|
1202
|
+
font-size: 1.5em;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
h3 {
|
|
1206
|
+
font-size: 1.17em;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
h4 {
|
|
1210
|
+
font-size: 1em;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
h5 {
|
|
1214
|
+
font-size: 0.83em;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
h6 {
|
|
1218
|
+
font-size: 0.67em;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
h1,
|
|
1222
|
+
h2,
|
|
1223
|
+
h3,
|
|
1224
|
+
h4,
|
|
1225
|
+
h5,
|
|
1226
|
+
h6 {
|
|
1227
|
+
font-weight: 700;
|
|
1228
|
+
line-height: 1.2;
|
|
1229
|
+
text-wrap: balance;
|
|
1230
|
+
overflow-wrap: break-word;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
p,
|
|
1234
|
+
li,
|
|
1235
|
+
figcaption,
|
|
1236
|
+
dd,
|
|
1237
|
+
dt,
|
|
1238
|
+
blockquote {
|
|
1239
|
+
overflow-wrap: break-word;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
p,
|
|
1243
|
+
li,
|
|
1244
|
+
figcaption,
|
|
1245
|
+
blockquote {
|
|
1246
|
+
text-wrap: pretty;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
ul,
|
|
1250
|
+
ol,
|
|
1251
|
+
menu {
|
|
1252
|
+
list-style: none;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
a {
|
|
1256
|
+
color: inherit;
|
|
1257
|
+
text-decoration: underline;
|
|
1258
|
+
text-decoration-thickness: 1px;
|
|
1259
|
+
text-underline-offset: 0.15em;
|
|
1260
|
+
text-decoration-skip-ink: auto;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
b,
|
|
1264
|
+
strong {
|
|
1265
|
+
font-weight: 700;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
small {
|
|
1269
|
+
font-size: 0.875em;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
code,
|
|
1273
|
+
kbd,
|
|
1274
|
+
samp,
|
|
1275
|
+
pre {
|
|
1276
|
+
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1277
|
+
font-size: 1em;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
sub,
|
|
1281
|
+
sup {
|
|
1282
|
+
font-size: 75%;
|
|
1283
|
+
line-height: 0;
|
|
1284
|
+
position: relative;
|
|
1285
|
+
vertical-align: baseline;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
sub {
|
|
1289
|
+
bottom: -0.25em;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
sup {
|
|
1293
|
+
top: -0.5em;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
abbr[title] {
|
|
1297
|
+
text-decoration: underline dotted;
|
|
1298
|
+
text-underline-offset: 0.15em;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
mark {
|
|
1302
|
+
background: Mark;
|
|
1303
|
+
color: MarkText;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
hr {
|
|
1307
|
+
height: 0;
|
|
1308
|
+
border: 0;
|
|
1309
|
+
border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1310
|
+
color: inherit;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
img,
|
|
1314
|
+
picture,
|
|
1315
|
+
video,
|
|
1316
|
+
canvas {
|
|
1317
|
+
display: block;
|
|
1318
|
+
max-width: 100%;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
img,
|
|
1322
|
+
video {
|
|
1323
|
+
height: auto;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
svg {
|
|
1327
|
+
overflow: hidden;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
iframe {
|
|
1331
|
+
border: 0;
|
|
1332
|
+
max-width: 100%;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
table {
|
|
1336
|
+
border-collapse: collapse;
|
|
1337
|
+
border-spacing: 0;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
fieldset {
|
|
1341
|
+
border: 0;
|
|
1342
|
+
min-inline-size: 0;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
legend {
|
|
1346
|
+
padding: 0;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
button,
|
|
1350
|
+
input,
|
|
1351
|
+
select,
|
|
1352
|
+
textarea,
|
|
1353
|
+
optgroup {
|
|
1354
|
+
font: inherit;
|
|
1355
|
+
letter-spacing: inherit;
|
|
1356
|
+
color: inherit;
|
|
1357
|
+
margin: 0;
|
|
1358
|
+
vertical-align: middle;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
button,
|
|
1362
|
+
input[type="button"],
|
|
1363
|
+
input[type="submit"],
|
|
1364
|
+
input[type="reset"] {
|
|
1365
|
+
appearance: none;
|
|
1366
|
+
background: none;
|
|
1367
|
+
border: 0;
|
|
1368
|
+
padding: 0;
|
|
1369
|
+
cursor: pointer;
|
|
1370
|
+
text-align: center;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
input,
|
|
1374
|
+
textarea,
|
|
1375
|
+
select {
|
|
1376
|
+
background-color: Canvas;
|
|
1377
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1378
|
+
border-radius: 0;
|
|
1379
|
+
padding: 0.5em 0.65em;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
input[type="checkbox"],
|
|
1383
|
+
input[type="radio"],
|
|
1384
|
+
input[type="range"],
|
|
1385
|
+
input[type="file"],
|
|
1386
|
+
input[type="color"],
|
|
1387
|
+
input[type="button"],
|
|
1388
|
+
input[type="submit"],
|
|
1389
|
+
input[type="reset"],
|
|
1390
|
+
input[type="image"],
|
|
1391
|
+
input[type="hidden"] {
|
|
1392
|
+
background: none;
|
|
1393
|
+
border: 0;
|
|
1394
|
+
padding: 0;
|
|
1395
|
+
border-radius: 0;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
input[type="checkbox"],
|
|
1399
|
+
input[type="radio"] {
|
|
1400
|
+
accent-color: CanvasText;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
input[type="search"] {
|
|
1404
|
+
appearance: none;
|
|
1405
|
+
background-color: Canvas;
|
|
1406
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
input[type="search"]::-webkit-search-decoration,
|
|
1410
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
1411
|
+
input[type="search"]::-webkit-search-results-button,
|
|
1412
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
1413
|
+
-webkit-appearance: none;
|
|
1414
|
+
display: none;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
input[type="number"] {
|
|
1418
|
+
appearance: textfield;
|
|
1419
|
+
background-color: Canvas;
|
|
1420
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
1424
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
1425
|
+
-webkit-appearance: none;
|
|
1426
|
+
margin: 0;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
textarea {
|
|
1430
|
+
resize: vertical;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
::placeholder {
|
|
1434
|
+
opacity: 1;
|
|
1435
|
+
color: color-mix(in srgb, CanvasText 45%, transparent);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
:focus-visible {
|
|
1439
|
+
outline: 2px solid CanvasText;
|
|
1440
|
+
outline-offset: 2px;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
:focus:not(:focus-visible) {
|
|
1444
|
+
outline: none;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
:disabled,
|
|
1448
|
+
[disabled] {
|
|
1449
|
+
cursor: default;
|
|
1450
|
+
opacity: 0.6;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
label,
|
|
1454
|
+
summary,
|
|
1455
|
+
select,
|
|
1456
|
+
[role="button"] {
|
|
1457
|
+
cursor: pointer;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
summary {
|
|
1461
|
+
display: list-item;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
details {
|
|
1465
|
+
display: block;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
dialog {
|
|
1469
|
+
margin: auto;
|
|
1470
|
+
padding: 1rem;
|
|
1471
|
+
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1472
|
+
background: Canvas;
|
|
1473
|
+
color: CanvasText;
|
|
1474
|
+
max-width: calc(100vw - 2rem);
|
|
1475
|
+
max-height: calc(100svh - 2rem);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
dialog:not([open]) {
|
|
1479
|
+
display: none;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
::backdrop {
|
|
1483
|
+
background: color-mix(in srgb, CanvasText 40%, transparent);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
[hidden] {
|
|
1487
|
+
display: none !important;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
::selection {
|
|
1491
|
+
background: Highlight;
|
|
1492
|
+
color: HighlightText;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1496
|
+
*,
|
|
1497
|
+
*::before,
|
|
1498
|
+
*::after {
|
|
1499
|
+
animation-duration: 0.01ms !important;
|
|
1500
|
+
animation-iteration-count: 1 !important;
|
|
1501
|
+
transition-duration: 0.01ms !important;
|
|
1502
|
+
scroll-behavior: auto !important;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
@media (pointer: coarse) {
|
|
1507
|
+
input,
|
|
1508
|
+
select,
|
|
1509
|
+
textarea {
|
|
1510
|
+
font-size: max(16px, 1em);
|
|
1511
|
+
}
|
|
1512
|
+
}`;
|
|
1513
|
+
|
|
1514
|
+
// ../core/dist/admin-client.js
|
|
1515
|
+
function isRecord2(value) {
|
|
1516
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1517
|
+
}
|
|
1518
|
+
function mountAdminBrand(doc, host, service, catalog) {
|
|
1519
|
+
const clip = (value, max) => {
|
|
1520
|
+
if (typeof value !== "string")
|
|
1521
|
+
return null;
|
|
1522
|
+
const trimmed = value.trim();
|
|
1523
|
+
if (trimmed === "")
|
|
1524
|
+
return null;
|
|
1525
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
1526
|
+
};
|
|
1527
|
+
const httpUrl = (value) => {
|
|
1528
|
+
if (typeof value !== "string" || value.length > 2e3)
|
|
1529
|
+
return null;
|
|
1530
|
+
try {
|
|
1531
|
+
const url = new URL(value);
|
|
1532
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1533
|
+
return null;
|
|
1534
|
+
return url.href;
|
|
1535
|
+
} catch {
|
|
1536
|
+
return null;
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1539
|
+
const hexColor = (value) => {
|
|
1540
|
+
if (typeof value !== "string")
|
|
1541
|
+
return null;
|
|
1542
|
+
const raw = value.trim();
|
|
1543
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
|
|
1544
|
+
};
|
|
1545
|
+
const hostname = (value) => {
|
|
1546
|
+
try {
|
|
1547
|
+
return new URL(value).hostname.replace(/^www\./, "");
|
|
1548
|
+
} catch {
|
|
1549
|
+
return value;
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
host.replaceChildren();
|
|
1553
|
+
host.hidden = true;
|
|
1554
|
+
if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
|
|
1555
|
+
return;
|
|
1556
|
+
const row = catalog[service];
|
|
1557
|
+
if (!isRecord2(row))
|
|
1558
|
+
return;
|
|
1559
|
+
const vendor = clip(row.vendor, 80);
|
|
1560
|
+
const website = httpUrl(row.website);
|
|
1561
|
+
const guide = httpUrl(row.guide);
|
|
1562
|
+
const docs = httpUrl(row.docs);
|
|
1563
|
+
const logo = httpUrl(row.logo);
|
|
1564
|
+
const color = hexColor(row.color);
|
|
1565
|
+
const description = clip(row.description, 280);
|
|
1566
|
+
if (vendor === null || website === null && guide === null && docs === null)
|
|
1567
|
+
return;
|
|
1568
|
+
const add = (node) => {
|
|
1569
|
+
host.append(node);
|
|
1570
|
+
return node;
|
|
1571
|
+
};
|
|
1572
|
+
if (logo !== null) {
|
|
1573
|
+
const img = doc.createElement("img");
|
|
1574
|
+
img.alt = "";
|
|
1575
|
+
img.src = logo;
|
|
1576
|
+
img.width = 16;
|
|
1577
|
+
img.height = 16;
|
|
1578
|
+
img.decoding = "async";
|
|
1579
|
+
img.referrerPolicy = "no-referrer";
|
|
1580
|
+
if (color !== null)
|
|
1581
|
+
img.style.borderColor = color;
|
|
1582
|
+
add(img);
|
|
1583
|
+
}
|
|
1584
|
+
const name = doc.createElement("span");
|
|
1585
|
+
name.className = "vendor-name";
|
|
1586
|
+
name.textContent = vendor;
|
|
1587
|
+
if (description !== null)
|
|
1588
|
+
name.title = description;
|
|
1589
|
+
add(name);
|
|
1590
|
+
const sep = () => {
|
|
1591
|
+
const node = doc.createElement("span");
|
|
1592
|
+
node.className = "vendor-sep";
|
|
1593
|
+
node.setAttribute("aria-hidden", "true");
|
|
1594
|
+
node.textContent = "\xB7";
|
|
1595
|
+
return node;
|
|
1596
|
+
};
|
|
1597
|
+
const link = (href, label, title) => {
|
|
1598
|
+
const node = doc.createElement("a");
|
|
1599
|
+
node.className = "vendor-link";
|
|
1600
|
+
node.href = href;
|
|
1601
|
+
node.target = "_blank";
|
|
1602
|
+
node.rel = "noopener noreferrer";
|
|
1603
|
+
node.referrerPolicy = "no-referrer";
|
|
1604
|
+
node.textContent = label;
|
|
1605
|
+
node.title = title;
|
|
1606
|
+
return node;
|
|
1607
|
+
};
|
|
1608
|
+
if (website !== null) {
|
|
1609
|
+
add(sep());
|
|
1610
|
+
add(link(website, hostname(website), `${vendor} website`));
|
|
1611
|
+
}
|
|
1612
|
+
if (guide !== null) {
|
|
1613
|
+
add(sep());
|
|
1614
|
+
add(link(guide, "Docs", `Docs for ${vendor}`));
|
|
1615
|
+
}
|
|
1616
|
+
if (docs !== null) {
|
|
1617
|
+
add(sep());
|
|
1618
|
+
add(link(docs, "API", `${vendor} API reference`));
|
|
1619
|
+
}
|
|
1620
|
+
host.removeAttribute("hidden");
|
|
1621
|
+
host.hidden = false;
|
|
1622
|
+
}
|
|
1623
|
+
function startAdminBrand(env) {
|
|
1624
|
+
const requested = env.params.get("brands");
|
|
1625
|
+
let brandsUrl = env.brandsUrl;
|
|
1626
|
+
if (requested) {
|
|
1627
|
+
try {
|
|
1628
|
+
const url = new URL(requested, env.location.href);
|
|
1629
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
|
|
1630
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && local)
|
|
1631
|
+
brandsUrl = url.href;
|
|
1632
|
+
} catch {
|
|
1633
|
+
brandsUrl = env.brandsUrl;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
const vendorHost = env.$("vendor");
|
|
1637
|
+
const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
|
|
1638
|
+
const brandService = brandRoot?.getAttribute("data-service") || "";
|
|
1639
|
+
if (vendorHost === null)
|
|
1640
|
+
return;
|
|
1641
|
+
env.fetch(brandsUrl, {
|
|
1642
|
+
headers: { accept: "application/json" },
|
|
1643
|
+
signal: AbortSignal.timeout(5e3)
|
|
1644
|
+
}).then(async (response) => {
|
|
1645
|
+
if (!response.ok)
|
|
1646
|
+
return null;
|
|
1647
|
+
const catalog = await response.json();
|
|
1648
|
+
return catalog;
|
|
1649
|
+
}).then((catalog) => {
|
|
1650
|
+
mountAdminBrand(env.document, vendorHost, brandService, catalog);
|
|
1651
|
+
}).catch(() => {
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
function faultPresetList(value) {
|
|
1655
|
+
if (!isRecord2(value))
|
|
1656
|
+
throw new Error("Fault presets response was not an object");
|
|
1657
|
+
if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
|
|
1658
|
+
return { presets: [] };
|
|
1659
|
+
}
|
|
1660
|
+
if (!Array.isArray(value.presets))
|
|
1661
|
+
throw new Error("Fault presets response was not a list");
|
|
1662
|
+
return {
|
|
1663
|
+
presets: value.presets.flatMap((item) => {
|
|
1664
|
+
if (!isRecord2(item) || typeof item.name !== "string")
|
|
1665
|
+
return [];
|
|
1666
|
+
return [{ name: item.name }];
|
|
1667
|
+
})
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
function bootAdmin(config) {
|
|
1671
|
+
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1672
|
+
const sources = ["declared", "inferred", "mixed"];
|
|
1673
|
+
const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
|
|
1674
|
+
const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
|
|
1675
|
+
const readJson2 = (text) => {
|
|
1676
|
+
const value = JSON.parse(text);
|
|
1677
|
+
return value;
|
|
1678
|
+
};
|
|
1679
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
|
|
1680
|
+
switch (char) {
|
|
1681
|
+
case "&":
|
|
1682
|
+
return "&";
|
|
1683
|
+
case "<":
|
|
1684
|
+
return "<";
|
|
1685
|
+
case ">":
|
|
1686
|
+
return ">";
|
|
1687
|
+
case '"':
|
|
1688
|
+
return """;
|
|
1689
|
+
default:
|
|
1690
|
+
return "'";
|
|
1691
|
+
}
|
|
1692
|
+
});
|
|
1693
|
+
const el = (id, kind) => {
|
|
1694
|
+
const node = document.getElementById(id);
|
|
1695
|
+
if (!(node instanceof kind))
|
|
1696
|
+
throw new Error(`admin shell is missing #${id}`);
|
|
1697
|
+
return node;
|
|
1698
|
+
};
|
|
1699
|
+
const errorMessage = (data, fallback) => {
|
|
1700
|
+
if (!isRecord2(data))
|
|
1701
|
+
return fallback;
|
|
1702
|
+
const error2 = data.error;
|
|
1703
|
+
if (!isRecord2(error2) || typeof error2.message !== "string" || error2.message === "") {
|
|
1704
|
+
return fallback;
|
|
1705
|
+
}
|
|
1706
|
+
return error2.message;
|
|
1707
|
+
};
|
|
1708
|
+
const requireList = (value, label) => {
|
|
1709
|
+
if (value === void 0 || value === null)
|
|
1710
|
+
return [];
|
|
1711
|
+
if (!Array.isArray(value))
|
|
1712
|
+
throw new Error(`${label} was not a list`);
|
|
1713
|
+
return value;
|
|
1714
|
+
};
|
|
1715
|
+
const state = {
|
|
1716
|
+
namespace: "default",
|
|
1717
|
+
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1718
|
+
view: "overview",
|
|
1719
|
+
collection: "",
|
|
1720
|
+
shape: null,
|
|
1721
|
+
route: null,
|
|
1722
|
+
editing: null
|
|
1723
|
+
};
|
|
1724
|
+
const standard = new Set(config.standardRoutes);
|
|
1725
|
+
const banner = el("banner", HTMLElement);
|
|
1726
|
+
const keyInput = el("admin-key", HTMLInputElement);
|
|
1727
|
+
keyInput.value = state.key;
|
|
1728
|
+
const params = new URLSearchParams(location.search);
|
|
1729
|
+
const queryKey = params.get("key");
|
|
1730
|
+
if (queryKey) {
|
|
1731
|
+
state.key = queryKey;
|
|
1732
|
+
keyInput.value = state.key;
|
|
1733
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
1734
|
+
params.delete("key");
|
|
1735
|
+
const next = params.toString();
|
|
1736
|
+
history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
|
|
1737
|
+
}
|
|
1738
|
+
const queryNamespace = params.get("namespace");
|
|
1739
|
+
if (queryNamespace)
|
|
1740
|
+
state.namespace = queryNamespace;
|
|
1741
|
+
const showError = (error2) => {
|
|
1742
|
+
banner.textContent = error2 instanceof Error ? error2.message : String(error2);
|
|
1743
|
+
banner.classList.add("show");
|
|
1744
|
+
};
|
|
1745
|
+
const clearError = () => {
|
|
1746
|
+
banner.classList.remove("show");
|
|
1747
|
+
};
|
|
1748
|
+
const api = async (method, path, body) => {
|
|
1749
|
+
const headers = { accept: "application/json" };
|
|
1750
|
+
if (state.key !== "")
|
|
1751
|
+
headers[config.adminKeyHeader] = state.key;
|
|
1752
|
+
if (state.namespace !== "")
|
|
1753
|
+
headers["x-mockingbird-namespace"] = state.namespace;
|
|
1754
|
+
if (body !== void 0)
|
|
1755
|
+
headers["content-type"] = "application/json";
|
|
1756
|
+
const init = { method, headers };
|
|
1757
|
+
if (body !== void 0)
|
|
1758
|
+
init.body = JSON.stringify(body);
|
|
1759
|
+
const response = await fetch(config.adminPrefix + path, init);
|
|
1760
|
+
const text = await response.text();
|
|
1761
|
+
let data = null;
|
|
1762
|
+
if (text !== "") {
|
|
1763
|
+
try {
|
|
1764
|
+
data = readJson2(text);
|
|
1765
|
+
} catch {
|
|
1766
|
+
data = { raw: text };
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
if (response.status === 401) {
|
|
1770
|
+
keyInput.focus();
|
|
1771
|
+
throw new Error(errorMessage(data, "Admin key required"));
|
|
1772
|
+
}
|
|
1773
|
+
if (!response.ok) {
|
|
1774
|
+
throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
|
|
1775
|
+
}
|
|
1776
|
+
return data;
|
|
1777
|
+
};
|
|
1778
|
+
const originGet = async (path) => {
|
|
1779
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
1780
|
+
if (!response.ok)
|
|
1781
|
+
throw new Error(`${response.status} ${path}`);
|
|
1782
|
+
const value = await response.json();
|
|
1783
|
+
return value;
|
|
1784
|
+
};
|
|
1785
|
+
const panelApi = {
|
|
1786
|
+
get: (path) => api("GET", path),
|
|
1787
|
+
send: (method, path, body) => api(method, path, body),
|
|
1788
|
+
namespace: () => state.namespace
|
|
1789
|
+
};
|
|
1790
|
+
const isPanelScript = (value) => typeof value === "function";
|
|
1791
|
+
const setTheme = (theme) => {
|
|
1792
|
+
if (theme)
|
|
1793
|
+
document.documentElement.dataset.theme = theme;
|
|
1794
|
+
else
|
|
1795
|
+
delete document.documentElement.dataset.theme;
|
|
1796
|
+
};
|
|
1797
|
+
const storedTheme = localStorage.getItem("mockingbird-admin-theme");
|
|
1798
|
+
if (storedTheme)
|
|
1799
|
+
setTheme(storedTheme);
|
|
1800
|
+
el("theme", HTMLButtonElement).addEventListener("click", () => {
|
|
1801
|
+
const current = document.documentElement.dataset.theme;
|
|
1802
|
+
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
1803
|
+
const next = dark ? "light" : "dark";
|
|
1804
|
+
localStorage.setItem("mockingbird-admin-theme", next);
|
|
1805
|
+
setTheme(next);
|
|
1806
|
+
});
|
|
1807
|
+
const show = (view) => {
|
|
1808
|
+
state.view = view;
|
|
1809
|
+
for (const button of document.querySelectorAll("#nav button")) {
|
|
1810
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1811
|
+
continue;
|
|
1812
|
+
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
|
|
1813
|
+
}
|
|
1814
|
+
for (const section of document.querySelectorAll("main .view")) {
|
|
1815
|
+
if (!(section instanceof HTMLElement))
|
|
1816
|
+
continue;
|
|
1817
|
+
section.hidden = section.id !== `view-${view}`;
|
|
1818
|
+
}
|
|
1819
|
+
};
|
|
1820
|
+
el("nav", HTMLElement).addEventListener("click", (event) => {
|
|
1821
|
+
const target = event.target;
|
|
1822
|
+
if (!(target instanceof Element))
|
|
1823
|
+
return;
|
|
1824
|
+
const button = target.closest("button[data-view]");
|
|
1825
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1826
|
+
return;
|
|
1827
|
+
const view = button.dataset.view;
|
|
1828
|
+
if (view === void 0)
|
|
1829
|
+
return;
|
|
1830
|
+
show(view);
|
|
1831
|
+
refresh().catch(showError);
|
|
1832
|
+
});
|
|
1833
|
+
const cards = (items) => `<div class="grid">${items.map((item) => `<article class="card"><div class="k">${item.k}</div><div class="v">${item.v}</div></article>`).join("")}</div>`;
|
|
1834
|
+
const table = (columns, rows) => {
|
|
1835
|
+
if (rows.length === 0)
|
|
1836
|
+
return '<p class="empty">Nothing here yet.</p>';
|
|
1837
|
+
const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
|
|
1838
|
+
const body = rows.map((row) => {
|
|
1839
|
+
const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
|
|
1840
|
+
const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
|
|
1841
|
+
return `<tr${attrs}>${cells}</tr>`;
|
|
1842
|
+
}).join("");
|
|
1843
|
+
return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
|
1844
|
+
};
|
|
1845
|
+
const preview = (value) => {
|
|
1846
|
+
const text = JSON.stringify(value) ?? "null";
|
|
1847
|
+
return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
|
|
1848
|
+
};
|
|
1849
|
+
const when = (epoch) => {
|
|
1850
|
+
const date = new Date(epoch);
|
|
1851
|
+
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
|
|
1852
|
+
};
|
|
1853
|
+
const clockCard = (epoch) => {
|
|
1854
|
+
const date = new Date(epoch);
|
|
1855
|
+
if (Number.isNaN(date.getTime()))
|
|
1856
|
+
return esc(String(epoch));
|
|
1857
|
+
const iso = date.toISOString();
|
|
1858
|
+
return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
|
|
1859
|
+
};
|
|
1860
|
+
const decodeClock = (value) => {
|
|
1861
|
+
if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
|
|
1862
|
+
throw new Error("Clock response was not a clock");
|
|
1863
|
+
}
|
|
1864
|
+
return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
|
|
1865
|
+
};
|
|
1866
|
+
const decodeField = (value) => {
|
|
1867
|
+
if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
|
|
1868
|
+
return void 0;
|
|
1869
|
+
}
|
|
1870
|
+
const field = {
|
|
1871
|
+
name: value.name,
|
|
1872
|
+
kind: value.kind,
|
|
1873
|
+
optional: value.optional === true
|
|
1874
|
+
};
|
|
1875
|
+
if (typeof value.description === "string")
|
|
1876
|
+
field.description = value.description;
|
|
1877
|
+
if (Array.isArray(value.fields)) {
|
|
1878
|
+
field.fields = value.fields.flatMap((item) => {
|
|
1879
|
+
const child = decodeField(item);
|
|
1880
|
+
return child === void 0 ? [] : [child];
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
return field;
|
|
1884
|
+
};
|
|
1885
|
+
const decodeCollection = (value) => {
|
|
1886
|
+
if (!isRecord2(value) || typeof value.name !== "string")
|
|
1887
|
+
return void 0;
|
|
1888
|
+
const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
|
|
1889
|
+
const field = decodeField(item);
|
|
1890
|
+
return field === void 0 ? [] : [field];
|
|
1891
|
+
}) : [];
|
|
1892
|
+
const collection = {
|
|
1893
|
+
name: value.name,
|
|
1894
|
+
label: typeof value.label === "string" ? value.label : value.name,
|
|
1895
|
+
count: typeof value.count === "number" ? value.count : 0,
|
|
1896
|
+
source: isSource(value.source) ? value.source : "inferred",
|
|
1897
|
+
fields
|
|
1898
|
+
};
|
|
1899
|
+
if (typeof value.description === "string")
|
|
1900
|
+
collection.description = value.description;
|
|
1901
|
+
return collection;
|
|
1902
|
+
};
|
|
1903
|
+
const decodeState = (value) => {
|
|
1904
|
+
if (!isRecord2(value) || !Array.isArray(value.collections)) {
|
|
1905
|
+
throw new Error("State response has no collection list");
|
|
1906
|
+
}
|
|
1907
|
+
return {
|
|
1908
|
+
namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
|
|
1909
|
+
storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
|
|
1910
|
+
collections: value.collections.flatMap((item) => {
|
|
1911
|
+
const collection = decodeCollection(item);
|
|
1912
|
+
return collection === void 0 ? [] : [collection];
|
|
1913
|
+
})
|
|
1914
|
+
};
|
|
1915
|
+
};
|
|
1916
|
+
const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
|
|
1917
|
+
if (!isRecord2(item) || typeof item.id !== "string")
|
|
1918
|
+
return [];
|
|
1919
|
+
return [
|
|
1920
|
+
{
|
|
1921
|
+
id: item.id,
|
|
1922
|
+
seq: typeof item.seq === "number" ? item.seq : 0,
|
|
1923
|
+
value: item.value,
|
|
1924
|
+
attrs: ` class="clickable" data-id="${esc(item.id)}"`
|
|
1925
|
+
}
|
|
1926
|
+
];
|
|
1927
|
+
});
|
|
1928
|
+
const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
|
|
1929
|
+
const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
|
|
1930
|
+
if (!isRecord2(item))
|
|
1931
|
+
return [];
|
|
1932
|
+
return [
|
|
1933
|
+
{
|
|
1934
|
+
at: textOf(item.at),
|
|
1935
|
+
method: textOf(item.method),
|
|
1936
|
+
path: textOf(item.path),
|
|
1937
|
+
status: textOf(item.status),
|
|
1938
|
+
durationMs: textOf(item.durationMs),
|
|
1939
|
+
operationId: textOf(item.operationId)
|
|
1940
|
+
}
|
|
1941
|
+
];
|
|
1942
|
+
});
|
|
1943
|
+
const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
|
|
1944
|
+
if (!isRecord2(item))
|
|
1945
|
+
return [];
|
|
1946
|
+
return [
|
|
1947
|
+
{
|
|
1948
|
+
id: textOf(item.id),
|
|
1949
|
+
method: typeof item.method === "string" ? item.method : void 0,
|
|
1950
|
+
pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
|
|
1951
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0,
|
|
1952
|
+
status: typeof item.status === "number" ? item.status : void 0,
|
|
1953
|
+
effect: typeof item.effect === "string" ? item.effect : void 0,
|
|
1954
|
+
drop: typeof item.drop === "boolean" ? item.drop : void 0,
|
|
1955
|
+
latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
|
|
1956
|
+
}
|
|
1957
|
+
];
|
|
1958
|
+
});
|
|
1959
|
+
const loadNamespaces = async () => {
|
|
1960
|
+
const data = await api("GET", "/namespaces");
|
|
1961
|
+
const select = el("namespace", HTMLSelectElement);
|
|
1962
|
+
const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
|
|
1963
|
+
const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
|
|
1964
|
+
const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
|
|
1965
|
+
if (!names.includes(state.namespace))
|
|
1966
|
+
names.unshift(state.namespace);
|
|
1967
|
+
select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
|
|
1968
|
+
select.value = state.namespace;
|
|
1969
|
+
};
|
|
1970
|
+
const renderOverview = async () => {
|
|
1971
|
+
const [health, shape, clock, journal] = await Promise.all([
|
|
1972
|
+
originGet("/health"),
|
|
1973
|
+
api("GET", "/state"),
|
|
1974
|
+
api("GET", "/clock"),
|
|
1975
|
+
api("GET", "/requests?limit=8")
|
|
1976
|
+
]);
|
|
1977
|
+
if (!isRecord2(health))
|
|
1978
|
+
throw new Error("Health response was not an object");
|
|
1979
|
+
const decoded = decodeState(shape);
|
|
1980
|
+
state.shape = decoded;
|
|
1981
|
+
const time = decodeClock(clock);
|
|
1982
|
+
el("overview-cards", HTMLElement).innerHTML = cards([
|
|
1983
|
+
{
|
|
1984
|
+
k: "Service",
|
|
1985
|
+
v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
|
|
1986
|
+
},
|
|
1987
|
+
{
|
|
1988
|
+
k: "Status",
|
|
1989
|
+
v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
|
|
1990
|
+
},
|
|
1991
|
+
{ k: "Namespace", v: esc(state.namespace) },
|
|
1992
|
+
{ k: "Collections", v: String(decoded.collections.length) },
|
|
1993
|
+
{
|
|
1994
|
+
k: "Records",
|
|
1995
|
+
v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
|
|
1996
|
+
},
|
|
1997
|
+
{ k: "Clock", v: clockCard(time.now) }
|
|
1998
|
+
]);
|
|
1999
|
+
el("overview-requests", HTMLElement).innerHTML = table([
|
|
2000
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2001
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2002
|
+
{
|
|
2003
|
+
label: "Status",
|
|
2004
|
+
cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
|
|
2005
|
+
},
|
|
2006
|
+
{ label: "Operation", cell: (row) => esc(row.operationId) }
|
|
2007
|
+
], decodeJournal(journal));
|
|
2008
|
+
};
|
|
2009
|
+
const schemaChips = (collection) => {
|
|
2010
|
+
if (collection.fields.length === 0) {
|
|
2011
|
+
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
|
|
2012
|
+
}
|
|
2013
|
+
return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
|
|
2014
|
+
};
|
|
2015
|
+
const renderState = async () => {
|
|
2016
|
+
const shape = decodeState(await api("GET", "/state"));
|
|
2017
|
+
state.shape = shape;
|
|
2018
|
+
const host = el("collections", HTMLElement);
|
|
2019
|
+
if (shape.collections.length === 0) {
|
|
2020
|
+
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
|
|
2021
|
+
el("schema", HTMLElement).innerHTML = "";
|
|
2022
|
+
el("records", HTMLElement).innerHTML = "";
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2026
|
+
const first = shape.collections[0];
|
|
2027
|
+
if (first === void 0)
|
|
2028
|
+
return;
|
|
2029
|
+
state.collection = first.name;
|
|
2030
|
+
}
|
|
2031
|
+
host.innerHTML = shape.collections.map((item) => `<button type="button" class="collection" data-collection="${esc(item.name)}" aria-current="${item.name === state.collection ? "true" : "false"}"><strong>${esc(item.label)}</strong><small>${esc(item.count)} \xB7 ${esc(item.source)}</small></button>`).join("");
|
|
2032
|
+
const selected = shape.collections.find((item) => item.name === state.collection);
|
|
2033
|
+
if (selected === void 0)
|
|
2034
|
+
return;
|
|
2035
|
+
const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
|
|
2036
|
+
el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
|
|
2037
|
+
const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
|
|
2038
|
+
el("records", HTMLElement).innerHTML = table([
|
|
2039
|
+
{ label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
|
|
2040
|
+
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2041
|
+
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2042
|
+
], decodeRecords(page));
|
|
2043
|
+
};
|
|
2044
|
+
const renderClock = async () => {
|
|
2045
|
+
const clock = decodeClock(await api("GET", "/clock"));
|
|
2046
|
+
el("clock-readout", HTMLElement).innerHTML = `<div class="k">Now</div><div class="v">${esc(when(clock.now))}</div><p>${esc(clock.now)} ms \xB7 offset ${esc(clock.offsetMs)} ms ${clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>"}</p>`;
|
|
2047
|
+
const freeze = el("freeze", HTMLButtonElement);
|
|
2048
|
+
freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
|
|
2049
|
+
freeze.dataset.frozen = clock.frozen ? "1" : "0";
|
|
2050
|
+
};
|
|
2051
|
+
const renderFaults = async () => {
|
|
2052
|
+
const [faults, presets] = await Promise.all([
|
|
2053
|
+
api("GET", "/faults"),
|
|
2054
|
+
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2055
|
+
]);
|
|
2056
|
+
const select = el("fault-preset", HTMLSelectElement);
|
|
2057
|
+
const current = select.value;
|
|
2058
|
+
const list = faultPresetList(presets).presets;
|
|
2059
|
+
select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
|
|
2060
|
+
select.value = current;
|
|
2061
|
+
el("fault-list", HTMLElement).innerHTML = table([
|
|
2062
|
+
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2063
|
+
{
|
|
2064
|
+
label: "Match",
|
|
2065
|
+
cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
|
|
2066
|
+
},
|
|
2067
|
+
{
|
|
2068
|
+
label: "Effect",
|
|
2069
|
+
cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
|
|
2070
|
+
}
|
|
2071
|
+
], decodeFaults(faults));
|
|
2072
|
+
};
|
|
2073
|
+
const renderJournal = async () => {
|
|
2074
|
+
const journal = await api("GET", "/requests?limit=100");
|
|
2075
|
+
el("journal", HTMLElement).innerHTML = table([
|
|
2076
|
+
{ label: "When", cell: (row) => esc(row.at) },
|
|
2077
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2078
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2079
|
+
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2080
|
+
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2081
|
+
], decodeJournal(journal));
|
|
2082
|
+
};
|
|
2083
|
+
const renderRoutes = async () => {
|
|
2084
|
+
const data = await api("GET", "/");
|
|
2085
|
+
if (!isRecord2(data))
|
|
2086
|
+
throw new Error("Route list was not an object");
|
|
2087
|
+
const routes = requireList(data.routes, "Routes").map((route) => String(route));
|
|
2088
|
+
el("route-list", HTMLElement).innerHTML = routes.map((route) => {
|
|
2089
|
+
const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
|
|
2090
|
+
const parts = route.split(" ");
|
|
2091
|
+
const method = parts[0] ?? "";
|
|
2092
|
+
const path = parts.slice(1).join(" ");
|
|
2093
|
+
return `<div class="route"><span class="method">${esc(method)}</span><span>${esc(path)}${extra}</span><button class="btn" type="button" data-route="${esc(route)}">Use</button></div>`;
|
|
2094
|
+
}).join("");
|
|
2095
|
+
};
|
|
2096
|
+
const renderers = {
|
|
2097
|
+
overview: renderOverview,
|
|
2098
|
+
state: renderState,
|
|
2099
|
+
clock: renderClock,
|
|
2100
|
+
faults: renderFaults,
|
|
2101
|
+
journal: renderJournal,
|
|
2102
|
+
routes: renderRoutes
|
|
2103
|
+
};
|
|
2104
|
+
async function refresh() {
|
|
2105
|
+
clearError();
|
|
2106
|
+
const render = renderers[state.view];
|
|
2107
|
+
if (render)
|
|
2108
|
+
await render();
|
|
2109
|
+
}
|
|
2110
|
+
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2111
|
+
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2112
|
+
refresh().catch(showError);
|
|
2113
|
+
});
|
|
2114
|
+
keyInput.addEventListener("change", () => {
|
|
2115
|
+
state.key = keyInput.value.trim();
|
|
2116
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2117
|
+
refresh().catch(showError);
|
|
2118
|
+
});
|
|
2119
|
+
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2120
|
+
refresh().catch(showError);
|
|
2121
|
+
});
|
|
2122
|
+
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2123
|
+
const target = event.target;
|
|
2124
|
+
if (!(target instanceof Element))
|
|
2125
|
+
return;
|
|
2126
|
+
const button = target.closest("[data-collection]");
|
|
2127
|
+
if (!(button instanceof HTMLElement))
|
|
2128
|
+
return;
|
|
2129
|
+
const collection = button.dataset.collection;
|
|
2130
|
+
if (collection === void 0)
|
|
2131
|
+
return;
|
|
2132
|
+
state.collection = collection;
|
|
2133
|
+
renderState().catch(showError);
|
|
2134
|
+
});
|
|
2135
|
+
el("records", HTMLElement).addEventListener("click", (event) => {
|
|
2136
|
+
const target = event.target;
|
|
2137
|
+
if (!(target instanceof Element))
|
|
2138
|
+
return;
|
|
2139
|
+
const row = target.closest("[data-id]");
|
|
2140
|
+
if (!(row instanceof HTMLElement))
|
|
2141
|
+
return;
|
|
2142
|
+
const id = row.dataset.id;
|
|
2143
|
+
if (id === void 0)
|
|
2144
|
+
return;
|
|
2145
|
+
openEditor(id).catch(showError);
|
|
2146
|
+
});
|
|
2147
|
+
const editor = el("editor", HTMLDialogElement);
|
|
2148
|
+
const editorId = el("editor-id", HTMLInputElement);
|
|
2149
|
+
const editorValue = el("editor-value", HTMLTextAreaElement);
|
|
2150
|
+
const editorDelete = el("editor-delete", HTMLButtonElement);
|
|
2151
|
+
async function openEditor(id) {
|
|
2152
|
+
state.editing = id;
|
|
2153
|
+
el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
|
|
2154
|
+
editorId.value = id ?? "";
|
|
2155
|
+
editorId.readOnly = id !== null;
|
|
2156
|
+
editorDelete.hidden = id === null;
|
|
2157
|
+
if (id) {
|
|
2158
|
+
const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
|
|
2159
|
+
if (!isRecord2(record))
|
|
2160
|
+
throw new Error("Record response was not an object");
|
|
2161
|
+
editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
|
|
2162
|
+
} else {
|
|
2163
|
+
editorValue.value = "{\n \n}";
|
|
2164
|
+
}
|
|
2165
|
+
editor.showModal();
|
|
2166
|
+
}
|
|
2167
|
+
el("new-record", HTMLButtonElement).addEventListener("click", () => {
|
|
2168
|
+
openEditor(null).catch(showError);
|
|
2169
|
+
});
|
|
2170
|
+
el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2171
|
+
const submitter = event.submitter;
|
|
2172
|
+
if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
|
|
2173
|
+
return;
|
|
2174
|
+
event.preventDefault();
|
|
2175
|
+
let value;
|
|
2176
|
+
try {
|
|
2177
|
+
value = readJson2(editorValue.value);
|
|
2178
|
+
} catch {
|
|
2179
|
+
showError(new Error("Value is not JSON"));
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
const id = editorId.value.trim();
|
|
2183
|
+
const path = `/state/${encodeURIComponent(state.collection)}`;
|
|
2184
|
+
const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
|
|
2185
|
+
request.then(() => {
|
|
2186
|
+
editor.close();
|
|
2187
|
+
return renderState();
|
|
2188
|
+
}).catch(showError);
|
|
2189
|
+
});
|
|
2190
|
+
editorDelete.addEventListener("click", () => {
|
|
2191
|
+
const id = state.editing;
|
|
2192
|
+
if (!id)
|
|
2193
|
+
return;
|
|
2194
|
+
api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
|
|
2195
|
+
editor.close();
|
|
2196
|
+
return renderState();
|
|
2197
|
+
}).catch(showError);
|
|
2198
|
+
});
|
|
2199
|
+
for (const button of document.querySelectorAll("[data-advance]")) {
|
|
2200
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2201
|
+
continue;
|
|
2202
|
+
button.addEventListener("click", () => {
|
|
2203
|
+
const raw = button.dataset.advance;
|
|
2204
|
+
if (raw === void 0)
|
|
2205
|
+
return;
|
|
2206
|
+
api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
el("freeze", HTMLButtonElement).addEventListener("click", () => {
|
|
2210
|
+
const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
|
|
2211
|
+
api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
|
|
2212
|
+
});
|
|
2213
|
+
el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
|
|
2214
|
+
api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
|
|
2215
|
+
});
|
|
2216
|
+
el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2217
|
+
event.preventDefault();
|
|
2218
|
+
const raw = el("clock-instant", HTMLInputElement).value.trim();
|
|
2219
|
+
const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
|
|
2220
|
+
api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
|
|
2221
|
+
});
|
|
2222
|
+
el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2223
|
+
event.preventDefault();
|
|
2224
|
+
const preset = el("fault-preset", HTMLSelectElement).value;
|
|
2225
|
+
if (preset !== "") {
|
|
2226
|
+
api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
|
|
2227
|
+
return;
|
|
2228
|
+
}
|
|
2229
|
+
const status = Number(el("fault-status", HTMLInputElement).value);
|
|
2230
|
+
if (!Number.isFinite(status)) {
|
|
2231
|
+
showError(new Error("Status must be a number, or choose a preset"));
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
const path = el("fault-path", HTMLInputElement).value;
|
|
2235
|
+
const method = el("fault-method", HTMLInputElement).value;
|
|
2236
|
+
api("POST", "/faults", {
|
|
2237
|
+
status,
|
|
2238
|
+
...path !== "" ? { pathPrefix: path } : {},
|
|
2239
|
+
...method !== "" ? { method } : {}
|
|
2240
|
+
}).then(() => renderFaults()).catch(showError);
|
|
2241
|
+
});
|
|
2242
|
+
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2243
|
+
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2244
|
+
});
|
|
2245
|
+
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2246
|
+
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2247
|
+
});
|
|
2248
|
+
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2249
|
+
const target = event.target;
|
|
2250
|
+
if (!(target instanceof Element))
|
|
2251
|
+
return;
|
|
2252
|
+
const button = target.closest("[data-route]");
|
|
2253
|
+
if (!(button instanceof HTMLElement))
|
|
2254
|
+
return;
|
|
2255
|
+
const route = button.dataset.route;
|
|
2256
|
+
if (route === void 0)
|
|
2257
|
+
return;
|
|
2258
|
+
state.route = route;
|
|
2259
|
+
el("route-title", HTMLElement).textContent = route;
|
|
2260
|
+
const method = route.split(" ")[0] ?? "";
|
|
2261
|
+
el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
|
|
2262
|
+
});
|
|
2263
|
+
el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2264
|
+
event.preventDefault();
|
|
2265
|
+
const send = async () => {
|
|
2266
|
+
if (state.route === null)
|
|
2267
|
+
throw new Error("Choose a route first");
|
|
2268
|
+
const parts = state.route.split(" ");
|
|
2269
|
+
const method = parts[0];
|
|
2270
|
+
const path = parts.slice(1).join(" ");
|
|
2271
|
+
if (method === void 0)
|
|
2272
|
+
throw new Error("Choose a route first");
|
|
2273
|
+
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
|
|
2274
|
+
let body;
|
|
2275
|
+
const routeBody = el("route-body", HTMLTextAreaElement);
|
|
2276
|
+
if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
|
|
2277
|
+
try {
|
|
2278
|
+
body = readJson2(routeBody.value);
|
|
2279
|
+
} catch {
|
|
2280
|
+
throw new Error("Body is not JSON");
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
const data = await api(method, concrete, body);
|
|
2284
|
+
el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
|
|
2285
|
+
};
|
|
2286
|
+
send().catch(showError);
|
|
2287
|
+
});
|
|
2288
|
+
const decodePanel = (value) => {
|
|
2289
|
+
if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
|
|
2290
|
+
return void 0;
|
|
2291
|
+
}
|
|
2292
|
+
const panel = {
|
|
2293
|
+
id: value.id,
|
|
2294
|
+
title: value.title,
|
|
2295
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
2296
|
+
html: typeof value.html === "string" ? value.html : ""
|
|
2297
|
+
};
|
|
2298
|
+
if (typeof value.script === "string")
|
|
2299
|
+
panel.script = value.script;
|
|
2300
|
+
return panel;
|
|
2301
|
+
};
|
|
2302
|
+
const mountPanels = (panels) => {
|
|
2303
|
+
const nav = el("nav", HTMLElement);
|
|
2304
|
+
const views = el("views", HTMLElement);
|
|
2305
|
+
for (const panel of panels) {
|
|
2306
|
+
const button = document.createElement("button");
|
|
2307
|
+
button.type = "button";
|
|
2308
|
+
button.dataset.view = panel.id;
|
|
2309
|
+
button.textContent = panel.title;
|
|
2310
|
+
nav.append(button);
|
|
2311
|
+
const section = document.createElement("section");
|
|
2312
|
+
section.className = "view";
|
|
2313
|
+
section.id = `view-${panel.id}`;
|
|
2314
|
+
section.hidden = true;
|
|
2315
|
+
const title = document.createElement("h2");
|
|
2316
|
+
title.textContent = panel.title;
|
|
2317
|
+
const lede = document.createElement("p");
|
|
2318
|
+
lede.className = "lede";
|
|
2319
|
+
lede.textContent = panel.description;
|
|
2320
|
+
const body = document.createElement("div");
|
|
2321
|
+
body.className = "panel-body";
|
|
2322
|
+
body.id = `panel-${panel.id}`;
|
|
2323
|
+
body.innerHTML = panel.html;
|
|
2324
|
+
section.append(title, lede, body);
|
|
2325
|
+
views.append(section);
|
|
2326
|
+
if (panel.script === void 0)
|
|
2327
|
+
continue;
|
|
2328
|
+
try {
|
|
2329
|
+
const compiled = new Function("root", "api", panel.script);
|
|
2330
|
+
if (!isPanelScript(compiled))
|
|
2331
|
+
throw new Error("panel script did not compile");
|
|
2332
|
+
compiled(body, panelApi);
|
|
2333
|
+
} catch (error2) {
|
|
2334
|
+
const note = document.createElement("p");
|
|
2335
|
+
note.className = "banner show";
|
|
2336
|
+
note.textContent = error2 instanceof Error ? error2.message : String(error2);
|
|
2337
|
+
body.append(note);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
startAdminBrand({
|
|
2342
|
+
document,
|
|
2343
|
+
$: (id) => document.getElementById(id),
|
|
2344
|
+
params,
|
|
2345
|
+
location: { href: location.href, origin: location.origin },
|
|
2346
|
+
fetch,
|
|
2347
|
+
brandsUrl: config.brandsUrl
|
|
2348
|
+
});
|
|
2349
|
+
loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
|
|
2350
|
+
const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
|
|
2351
|
+
const decoded = decodePanel(panel);
|
|
2352
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2353
|
+
});
|
|
2354
|
+
mountPanels(panels);
|
|
2355
|
+
return refresh();
|
|
2356
|
+
}).catch(showError);
|
|
2357
|
+
}
|
|
2358
|
+
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2359
|
+
var adminClientSource = (config) => {
|
|
2360
|
+
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2361
|
+
return `<script>
|
|
2362
|
+
${clientPrelude}
|
|
2363
|
+
bootAdmin(${payload});
|
|
2364
|
+
</script>`;
|
|
2365
|
+
};
|
|
2366
|
+
|
|
1141
2367
|
// ../core/dist/surface.js
|
|
1142
2368
|
var defineMock = (runtime) => runtime;
|
|
1143
2369
|
var STANDARD_ADMIN_ROUTES = [
|
|
@@ -1206,94 +2432,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
|
1206
2432
|
return "'";
|
|
1207
2433
|
}
|
|
1208
2434
|
});
|
|
2435
|
+
var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
|
|
1209
2436
|
var renderAdminDocument = (service) => {
|
|
1210
2437
|
const name = escapeHtml(service);
|
|
1211
|
-
const
|
|
2438
|
+
const script = adminClientSource({
|
|
2439
|
+
standardRoutes: STANDARD_ADMIN_ROUTES,
|
|
2440
|
+
adminPrefix: ADMIN_PREFIX,
|
|
2441
|
+
adminKeyHeader: ADMIN_KEY_HEADER,
|
|
2442
|
+
brandsUrl: ADMIN_BRANDS_URL,
|
|
2443
|
+
service
|
|
2444
|
+
});
|
|
1212
2445
|
return `<!DOCTYPE html>
|
|
1213
2446
|
<html lang="en">
|
|
1214
2447
|
<head>
|
|
1215
2448
|
<meta charset="utf-8">
|
|
1216
2449
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1217
2450
|
<meta name="color-scheme" content="light dark">
|
|
1218
|
-
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='
|
|
2451
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='2' fill='%2327272a'/%3E%3C/svg%3E">
|
|
1219
2452
|
<title>${name} admin</title>
|
|
1220
2453
|
<style>
|
|
2454
|
+
${CSS_RESET}
|
|
1221
2455
|
:root {
|
|
1222
2456
|
color-scheme: light;
|
|
1223
|
-
--bg: #
|
|
2457
|
+
--bg: #fafafa;
|
|
1224
2458
|
--bg-elev: #ffffff;
|
|
1225
|
-
--bg-muted: #
|
|
1226
|
-
--bg-inset: #
|
|
1227
|
-
--line: #
|
|
1228
|
-
--line-strong: #
|
|
1229
|
-
--ink: #
|
|
1230
|
-
--muted: #
|
|
1231
|
-
--faint: #
|
|
1232
|
-
--accent: #
|
|
1233
|
-
--accent-ink: #
|
|
1234
|
-
--accent-soft: #
|
|
1235
|
-
--ok: #
|
|
1236
|
-
--ok-soft: #
|
|
1237
|
-
--warn: #
|
|
1238
|
-
--warn-soft: #
|
|
1239
|
-
--err: #
|
|
1240
|
-
--err-soft: #
|
|
1241
|
-
--shadow:
|
|
1242
|
-
--sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2459
|
+
--bg-muted: #f4f4f5;
|
|
2460
|
+
--bg-inset: #f4f4f5;
|
|
2461
|
+
--line: #dedee3;
|
|
2462
|
+
--line-strong: #c4c4cc;
|
|
2463
|
+
--ink: #18181b;
|
|
2464
|
+
--muted: #62626b;
|
|
2465
|
+
--faint: #71717a;
|
|
2466
|
+
--accent: #27272a;
|
|
2467
|
+
--accent-ink: #ffffff;
|
|
2468
|
+
--accent-soft: #f4f4f5;
|
|
2469
|
+
--ok: #166534;
|
|
2470
|
+
--ok-soft: #f0fdf4;
|
|
2471
|
+
--warn: #a16207;
|
|
2472
|
+
--warn-soft: #fefce8;
|
|
2473
|
+
--err: #a82d32;
|
|
2474
|
+
--err-soft: #fef2f2;
|
|
2475
|
+
--shadow: none;
|
|
2476
|
+
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1243
2477
|
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1244
|
-
--radius:
|
|
1245
|
-
--header:
|
|
1246
|
-
--nav:
|
|
2478
|
+
--radius: 10px;
|
|
2479
|
+
--header: 56px;
|
|
2480
|
+
--nav: 200px;
|
|
1247
2481
|
}
|
|
1248
2482
|
:root[data-theme="dark"] {
|
|
1249
2483
|
color-scheme: dark;
|
|
1250
|
-
--bg: #
|
|
1251
|
-
--bg-elev: #
|
|
1252
|
-
--bg-muted: #
|
|
1253
|
-
--bg-inset: #
|
|
1254
|
-
--line: #
|
|
1255
|
-
--line-strong: #
|
|
1256
|
-
--ink: #
|
|
1257
|
-
--muted: #
|
|
1258
|
-
--faint: #
|
|
1259
|
-
--accent: #
|
|
1260
|
-
--accent-ink: #
|
|
1261
|
-
--accent-soft: #
|
|
1262
|
-
--ok: #
|
|
1263
|
-
--ok-soft: #
|
|
1264
|
-
--warn: #
|
|
1265
|
-
--warn-soft: #
|
|
1266
|
-
--err: #
|
|
1267
|
-
--err-soft: #
|
|
1268
|
-
--shadow:
|
|
2484
|
+
--bg: #111113;
|
|
2485
|
+
--bg-elev: #19191c;
|
|
2486
|
+
--bg-muted: #242428;
|
|
2487
|
+
--bg-inset: #111113;
|
|
2488
|
+
--line: #36363c;
|
|
2489
|
+
--line-strong: #52525b;
|
|
2490
|
+
--ink: #f4f4f5;
|
|
2491
|
+
--muted: #a9a9b2;
|
|
2492
|
+
--faint: #71717a;
|
|
2493
|
+
--accent: #e4e4e7;
|
|
2494
|
+
--accent-ink: #18181b;
|
|
2495
|
+
--accent-soft: #242428;
|
|
2496
|
+
--ok: #86efac;
|
|
2497
|
+
--ok-soft: #14241b;
|
|
2498
|
+
--warn: #fde68a;
|
|
2499
|
+
--warn-soft: #2a2410;
|
|
2500
|
+
--err: #ffaaaa;
|
|
2501
|
+
--err-soft: #2c1516;
|
|
2502
|
+
--shadow: none;
|
|
1269
2503
|
}
|
|
1270
2504
|
@media (prefers-color-scheme: dark) {
|
|
1271
2505
|
:root:not([data-theme="light"]) {
|
|
1272
2506
|
color-scheme: dark;
|
|
1273
|
-
--bg: #
|
|
1274
|
-
--bg-elev: #
|
|
1275
|
-
--bg-muted: #
|
|
1276
|
-
--bg-inset: #
|
|
1277
|
-
--line: #
|
|
1278
|
-
--line-strong: #
|
|
1279
|
-
--ink: #
|
|
1280
|
-
--muted: #
|
|
1281
|
-
--faint: #
|
|
1282
|
-
--accent: #
|
|
1283
|
-
--accent-ink: #
|
|
1284
|
-
--accent-soft: #
|
|
1285
|
-
--ok: #
|
|
1286
|
-
--ok-soft: #
|
|
1287
|
-
--warn: #
|
|
1288
|
-
--warn-soft: #
|
|
1289
|
-
--err: #
|
|
1290
|
-
--err-soft: #
|
|
1291
|
-
--shadow:
|
|
2507
|
+
--bg: #111113;
|
|
2508
|
+
--bg-elev: #19191c;
|
|
2509
|
+
--bg-muted: #242428;
|
|
2510
|
+
--bg-inset: #111113;
|
|
2511
|
+
--line: #36363c;
|
|
2512
|
+
--line-strong: #52525b;
|
|
2513
|
+
--ink: #f4f4f5;
|
|
2514
|
+
--muted: #a9a9b2;
|
|
2515
|
+
--faint: #71717a;
|
|
2516
|
+
--accent: #e4e4e7;
|
|
2517
|
+
--accent-ink: #18181b;
|
|
2518
|
+
--accent-soft: #242428;
|
|
2519
|
+
--ok: #86efac;
|
|
2520
|
+
--ok-soft: #14241b;
|
|
2521
|
+
--warn: #fde68a;
|
|
2522
|
+
--warn-soft: #2a2410;
|
|
2523
|
+
--err: #ffaaaa;
|
|
2524
|
+
--err-soft: #2c1516;
|
|
2525
|
+
--shadow: none;
|
|
1292
2526
|
}
|
|
1293
2527
|
}
|
|
1294
|
-
|
|
1295
|
-
html
|
|
2528
|
+
/* Fill the viewport, then grow with the content so the background covers the scroll. */
|
|
2529
|
+
html { height: 100%; }
|
|
1296
2530
|
body {
|
|
2531
|
+
margin: 0;
|
|
2532
|
+
min-height: 100%;
|
|
2533
|
+
display: flex;
|
|
2534
|
+
flex-direction: column;
|
|
1297
2535
|
font-family: var(--sans);
|
|
1298
2536
|
background: var(--bg);
|
|
1299
2537
|
color: var(--ink);
|
|
@@ -1302,24 +2540,37 @@ var renderAdminDocument = (service) => {
|
|
|
1302
2540
|
padding-right: env(safe-area-inset-right);
|
|
1303
2541
|
-webkit-text-size-adjust: 100%;
|
|
1304
2542
|
}
|
|
1305
|
-
button, input, select, textarea { font: inherit; color: inherit; }
|
|
1306
|
-
button { cursor: pointer; }
|
|
1307
2543
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1308
|
-
|
|
1309
|
-
* { scroll-behavior: auto !important; transition: none !important; }
|
|
1310
|
-
}
|
|
1311
|
-
.app { min-height: 100%; display: flex; flex-direction: column; }
|
|
2544
|
+
.app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
|
|
1312
2545
|
header.top {
|
|
1313
2546
|
position: sticky; top: 0; z-index: 5;
|
|
1314
2547
|
display: flex; align-items: center; gap: 12px;
|
|
1315
2548
|
min-height: var(--header); padding: 10px 16px;
|
|
1316
|
-
background:
|
|
2549
|
+
background: var(--bg-elev);
|
|
1317
2550
|
border-bottom: 1px solid var(--line);
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
.brand {
|
|
1321
|
-
|
|
1322
|
-
|
|
2551
|
+
min-width: 0; max-width: 100%;
|
|
2552
|
+
}
|
|
2553
|
+
.brand {
|
|
2554
|
+
display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
|
|
2555
|
+
min-width: 0; max-width: 100%; margin-right: auto;
|
|
2556
|
+
}
|
|
2557
|
+
.brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
|
|
2558
|
+
.brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
|
|
2559
|
+
.brand-id span { color: var(--faint); font-size: 12px; }
|
|
2560
|
+
.vendor {
|
|
2561
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
2562
|
+
min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
|
|
2563
|
+
}
|
|
2564
|
+
.vendor[hidden] { display: none; }
|
|
2565
|
+
/* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
|
|
2566
|
+
.vendor img {
|
|
2567
|
+
width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
|
|
2568
|
+
border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
|
|
2569
|
+
}
|
|
2570
|
+
.vendor-name { color: var(--ink); font-weight: 600; }
|
|
2571
|
+
.vendor-sep { color: var(--faint); }
|
|
2572
|
+
.vendor-link { color: inherit; text-decoration: none; }
|
|
2573
|
+
.vendor-link:hover { color: var(--ink); text-decoration: underline; }
|
|
1323
2574
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1324
2575
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
1325
2576
|
label.field input, label.field select, .controls input[type="password"] {
|
|
@@ -1337,7 +2588,7 @@ var renderAdminDocument = (service) => {
|
|
|
1337
2588
|
record or the full nav row would widen the page past the viewport. */
|
|
1338
2589
|
.layout {
|
|
1339
2590
|
display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
|
|
1340
|
-
flex: 1
|
|
2591
|
+
flex: 1 0 auto; min-width: 0;
|
|
1341
2592
|
}
|
|
1342
2593
|
nav.side {
|
|
1343
2594
|
border-right: 1px solid var(--line); padding: 12px;
|
|
@@ -1391,8 +2642,8 @@ var renderAdminDocument = (service) => {
|
|
|
1391
2642
|
.banner.show { display: block; }
|
|
1392
2643
|
.empty { color: var(--muted); padding: 24px 8px; }
|
|
1393
2644
|
dialog {
|
|
1394
|
-
border: 1px solid var(--line); border-radius:
|
|
1395
|
-
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow:
|
|
2645
|
+
border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
|
|
2646
|
+
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
|
|
1396
2647
|
}
|
|
1397
2648
|
dialog::backdrop { background: rgb(20 19 17 / 0.45); }
|
|
1398
2649
|
dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1419,7 +2670,7 @@ var renderAdminDocument = (service) => {
|
|
|
1419
2670
|
padding-top: max(10px, env(safe-area-inset-top));
|
|
1420
2671
|
}
|
|
1421
2672
|
input, select, textarea { font-size: 16px; }
|
|
1422
|
-
.controls { width: 100%; }
|
|
2673
|
+
.brand, .vendor, .controls { width: 100%; }
|
|
1423
2674
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
1424
2675
|
.layout { grid-template-columns: minmax(0, 1fr); }
|
|
1425
2676
|
nav.side {
|
|
@@ -1450,8 +2701,11 @@ var renderAdminDocument = (service) => {
|
|
|
1450
2701
|
<div class="app" data-service="${name}" data-mockingbird-admin>
|
|
1451
2702
|
<header class="top">
|
|
1452
2703
|
<div class="brand">
|
|
1453
|
-
<
|
|
1454
|
-
|
|
2704
|
+
<div class="brand-id">
|
|
2705
|
+
<strong>${name}</strong>
|
|
2706
|
+
<span>Admin</span>
|
|
2707
|
+
</div>
|
|
2708
|
+
<div class="vendor" id="vendor" hidden></div>
|
|
1455
2709
|
</div>
|
|
1456
2710
|
<div class="controls">
|
|
1457
2711
|
<label class="field">Namespace
|
|
@@ -1581,460 +2835,7 @@ var renderAdminDocument = (service) => {
|
|
|
1581
2835
|
</div>
|
|
1582
2836
|
</form>
|
|
1583
2837
|
</dialog>
|
|
1584
|
-
|
|
1585
|
-
(() => {
|
|
1586
|
-
const STANDARD = new Set(${standard})
|
|
1587
|
-
const state = {
|
|
1588
|
-
namespace: "default",
|
|
1589
|
-
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1590
|
-
view: "overview",
|
|
1591
|
-
collection: "",
|
|
1592
|
-
shape: null,
|
|
1593
|
-
route: null,
|
|
1594
|
-
editing: null
|
|
1595
|
-
}
|
|
1596
|
-
const $ = (id) => document.getElementById(id)
|
|
1597
|
-
const banner = $("banner")
|
|
1598
|
-
const keyInput = $("admin-key")
|
|
1599
|
-
keyInput.value = state.key
|
|
1600
|
-
const params = new URLSearchParams(location.search)
|
|
1601
|
-
if (params.get("key")) {
|
|
1602
|
-
state.key = params.get("key")
|
|
1603
|
-
keyInput.value = state.key
|
|
1604
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1605
|
-
params.delete("key")
|
|
1606
|
-
const next = params.toString()
|
|
1607
|
-
history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
|
|
1608
|
-
}
|
|
1609
|
-
if (params.get("namespace")) state.namespace = params.get("namespace")
|
|
1610
|
-
|
|
1611
|
-
const showError = (error) => {
|
|
1612
|
-
banner.textContent = error instanceof Error ? error.message : String(error)
|
|
1613
|
-
banner.classList.add("show")
|
|
1614
|
-
}
|
|
1615
|
-
const clearError = () => banner.classList.remove("show")
|
|
1616
|
-
|
|
1617
|
-
async function originGet(path) {
|
|
1618
|
-
const response = await fetch(path, { headers: { accept: "application/json" } })
|
|
1619
|
-
if (!response.ok) throw new Error(response.status + " " + path)
|
|
1620
|
-
return response.json()
|
|
1621
|
-
}
|
|
1622
|
-
async function api(method, path, body) {
|
|
1623
|
-
const headers = { accept: "application/json" }
|
|
1624
|
-
if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
|
|
1625
|
-
if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
|
|
1626
|
-
if (body !== undefined) headers["content-type"] = "application/json"
|
|
1627
|
-
const response = await fetch("${ADMIN_PREFIX}" + path, {
|
|
1628
|
-
method,
|
|
1629
|
-
headers,
|
|
1630
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1631
|
-
})
|
|
1632
|
-
const text = await response.text()
|
|
1633
|
-
let data = null
|
|
1634
|
-
if (text) {
|
|
1635
|
-
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
|
1636
|
-
}
|
|
1637
|
-
if (response.status === 401) {
|
|
1638
|
-
keyInput.focus()
|
|
1639
|
-
throw new Error((data && data.error && data.error.message) || "Admin key required")
|
|
1640
|
-
}
|
|
1641
|
-
if (!response.ok) {
|
|
1642
|
-
throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
|
|
1643
|
-
}
|
|
1644
|
-
return data
|
|
1645
|
-
}
|
|
1646
|
-
const panelApi = {
|
|
1647
|
-
get: (path) => api("GET", path),
|
|
1648
|
-
send: (method, path, body) => api(method, path, body),
|
|
1649
|
-
namespace: () => state.namespace
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
function setTheme(theme) {
|
|
1653
|
-
if (theme) document.documentElement.dataset.theme = theme
|
|
1654
|
-
else delete document.documentElement.dataset.theme
|
|
1655
|
-
}
|
|
1656
|
-
const storedTheme = localStorage.getItem("mockingbird-admin-theme")
|
|
1657
|
-
if (storedTheme) setTheme(storedTheme)
|
|
1658
|
-
$("theme").addEventListener("click", () => {
|
|
1659
|
-
const current = document.documentElement.dataset.theme
|
|
1660
|
-
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
|
|
1661
|
-
const next = dark ? "light" : "dark"
|
|
1662
|
-
localStorage.setItem("mockingbird-admin-theme", next)
|
|
1663
|
-
setTheme(next)
|
|
1664
|
-
})
|
|
1665
|
-
|
|
1666
|
-
function show(view) {
|
|
1667
|
-
state.view = view
|
|
1668
|
-
for (const button of document.querySelectorAll("#nav button")) {
|
|
1669
|
-
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
|
|
1670
|
-
}
|
|
1671
|
-
for (const section of document.querySelectorAll("main .view")) {
|
|
1672
|
-
section.hidden = section.id !== "view-" + view
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
document.getElementById("nav").addEventListener("click", (event) => {
|
|
1676
|
-
const button = event.target.closest("button[data-view]")
|
|
1677
|
-
if (!button) return
|
|
1678
|
-
show(button.dataset.view)
|
|
1679
|
-
refresh().catch(showError)
|
|
1680
|
-
})
|
|
1681
|
-
|
|
1682
|
-
function cards(items) {
|
|
1683
|
-
return '<div class="grid">' + items.map((item) =>
|
|
1684
|
-
'<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
|
|
1685
|
-
).join("") + "</div>"
|
|
1686
|
-
}
|
|
1687
|
-
function table(columns, rows) {
|
|
1688
|
-
if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
|
|
1689
|
-
const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
|
|
1690
|
-
const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
|
|
1691
|
-
'<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
|
|
1692
|
-
).join("") + "</tr>").join("")
|
|
1693
|
-
return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
|
|
1694
|
-
}
|
|
1695
|
-
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
1696
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
1697
|
-
}[char]))
|
|
1698
|
-
const preview = (value) => {
|
|
1699
|
-
const text = JSON.stringify(value)
|
|
1700
|
-
return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
|
|
1701
|
-
}
|
|
1702
|
-
const when = (epoch) => {
|
|
1703
|
-
const date = new Date(epoch)
|
|
1704
|
-
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
|
|
1705
|
-
}
|
|
1706
|
-
const clockCard = (epoch) => {
|
|
1707
|
-
const date = new Date(epoch)
|
|
1708
|
-
if (Number.isNaN(date.getTime())) return esc(String(epoch))
|
|
1709
|
-
const iso = date.toISOString()
|
|
1710
|
-
return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
|
|
1711
|
-
}
|
|
1712
|
-
|
|
1713
|
-
async function loadNamespaces() {
|
|
1714
|
-
const data = await api("GET", "/namespaces")
|
|
1715
|
-
const select = $("namespace")
|
|
1716
|
-
const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
|
|
1717
|
-
if (!names.includes(state.namespace)) names.unshift(state.namespace)
|
|
1718
|
-
select.innerHTML = names.map((name) =>
|
|
1719
|
-
'<option value="' + esc(name) + '">' + esc(name) + "</option>"
|
|
1720
|
-
).join("")
|
|
1721
|
-
select.value = state.namespace
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
async function renderOverview() {
|
|
1725
|
-
const [health, shape, clock, journal] = await Promise.all([
|
|
1726
|
-
originGet("/health"),
|
|
1727
|
-
api("GET", "/state"),
|
|
1728
|
-
api("GET", "/clock"),
|
|
1729
|
-
api("GET", "/requests?limit=8")
|
|
1730
|
-
])
|
|
1731
|
-
state.shape = shape
|
|
1732
|
-
$("overview-cards").innerHTML = cards([
|
|
1733
|
-
{ k: "Service", v: esc(health.service || "${name}") },
|
|
1734
|
-
{ k: "Status", v: esc(health.status || "ok") },
|
|
1735
|
-
{ k: "Namespace", v: esc(state.namespace) },
|
|
1736
|
-
{ k: "Collections", v: String(shape.collections.length) },
|
|
1737
|
-
{ k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
|
|
1738
|
-
{ k: "Clock", v: clockCard(clock.now) }
|
|
1739
|
-
])
|
|
1740
|
-
const requests = (journal && journal.requests) || []
|
|
1741
|
-
$("overview-requests").innerHTML = table(
|
|
1742
|
-
[
|
|
1743
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1744
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1745
|
-
{ label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
|
|
1746
|
-
{ label: "Operation", cell: (row) => esc(row.operationId || "") }
|
|
1747
|
-
],
|
|
1748
|
-
requests
|
|
1749
|
-
)
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
function schemaChips(collection) {
|
|
1753
|
-
if (!collection || collection.fields.length === 0) {
|
|
1754
|
-
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
|
|
1755
|
-
}
|
|
1756
|
-
return '<div class="chips">' + collection.fields.map((field) =>
|
|
1757
|
-
'<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
|
|
1758
|
-
).join("") + "</div>"
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
async function renderState() {
|
|
1762
|
-
const shape = await api("GET", "/state")
|
|
1763
|
-
state.shape = shape
|
|
1764
|
-
const host = $("collections")
|
|
1765
|
-
if (shape.collections.length === 0) {
|
|
1766
|
-
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
|
|
1767
|
-
$("schema").innerHTML = ""
|
|
1768
|
-
$("records").innerHTML = ""
|
|
1769
|
-
return
|
|
1770
|
-
}
|
|
1771
|
-
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
1772
|
-
state.collection = shape.collections[0].name
|
|
1773
|
-
}
|
|
1774
|
-
host.innerHTML = shape.collections.map((item) =>
|
|
1775
|
-
'<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
|
|
1776
|
-
(item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
|
|
1777
|
-
'</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
|
|
1778
|
-
).join("")
|
|
1779
|
-
const selected = shape.collections.find((item) => item.name === state.collection)
|
|
1780
|
-
$("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
|
|
1781
|
-
(selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
|
|
1782
|
-
schemaChips(selected)
|
|
1783
|
-
const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
|
|
1784
|
-
const rows = page.records.map((record) => ({
|
|
1785
|
-
...record,
|
|
1786
|
-
attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
|
|
1787
|
-
}))
|
|
1788
|
-
$("records").innerHTML = table(
|
|
1789
|
-
[
|
|
1790
|
-
{ label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
|
|
1791
|
-
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
1792
|
-
{ label: "Value", cell: (row) => preview(row.value) }
|
|
1793
|
-
],
|
|
1794
|
-
rows
|
|
1795
|
-
)
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
async function renderClock() {
|
|
1799
|
-
const clock = await api("GET", "/clock")
|
|
1800
|
-
$("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
|
|
1801
|
-
"</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
|
|
1802
|
-
(clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
|
|
1803
|
-
$("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
|
|
1804
|
-
$("freeze").dataset.frozen = clock.frozen ? "1" : "0"
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
async function renderFaults() {
|
|
1808
|
-
const [faults, presets] = await Promise.all([
|
|
1809
|
-
api("GET", "/faults"),
|
|
1810
|
-
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
1811
|
-
])
|
|
1812
|
-
const select = $("fault-preset")
|
|
1813
|
-
const current = select.value
|
|
1814
|
-
const list = presets.presets || []
|
|
1815
|
-
select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
|
|
1816
|
-
'<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
|
|
1817
|
-
).join("")
|
|
1818
|
-
select.value = current
|
|
1819
|
-
const rules = faults.faults || []
|
|
1820
|
-
$("fault-list").innerHTML = table(
|
|
1821
|
-
[
|
|
1822
|
-
{ label: "Id", cell: (row) => esc(row.id) },
|
|
1823
|
-
{ label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
|
|
1824
|
-
{ label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
|
|
1825
|
-
],
|
|
1826
|
-
rules
|
|
1827
|
-
)
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
async function renderJournal() {
|
|
1831
|
-
const journal = await api("GET", "/requests?limit=100")
|
|
1832
|
-
$("journal").innerHTML = table(
|
|
1833
|
-
[
|
|
1834
|
-
{ label: "When", cell: (row) => esc(row.at || "") },
|
|
1835
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1836
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1837
|
-
{ label: "Status", cell: (row) => esc(row.status) },
|
|
1838
|
-
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
1839
|
-
],
|
|
1840
|
-
journal.requests || []
|
|
1841
|
-
)
|
|
1842
|
-
}
|
|
1843
|
-
|
|
1844
|
-
async function renderRoutes() {
|
|
1845
|
-
const data = await api("GET", "/")
|
|
1846
|
-
const routes = data.routes || []
|
|
1847
|
-
$("route-list").innerHTML = routes.map((route) => {
|
|
1848
|
-
const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
|
|
1849
|
-
return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
|
|
1850
|
-
'</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
|
|
1851
|
-
'</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
|
|
1852
|
-
}).join("")
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
const renderers = {
|
|
1856
|
-
overview: renderOverview,
|
|
1857
|
-
state: renderState,
|
|
1858
|
-
clock: renderClock,
|
|
1859
|
-
faults: renderFaults,
|
|
1860
|
-
journal: renderJournal,
|
|
1861
|
-
routes: renderRoutes
|
|
1862
|
-
}
|
|
1863
|
-
|
|
1864
|
-
async function refresh() {
|
|
1865
|
-
clearError()
|
|
1866
|
-
if (state.view in renderers) await renderers[state.view]()
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
$("namespace").addEventListener("change", () => {
|
|
1870
|
-
state.namespace = $("namespace").value
|
|
1871
|
-
refresh().catch(showError)
|
|
1872
|
-
})
|
|
1873
|
-
keyInput.addEventListener("change", () => {
|
|
1874
|
-
state.key = keyInput.value.trim()
|
|
1875
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1876
|
-
refresh().catch(showError)
|
|
1877
|
-
})
|
|
1878
|
-
$("refresh").addEventListener("click", () => refresh().catch(showError))
|
|
1879
|
-
|
|
1880
|
-
$("collections").addEventListener("click", (event) => {
|
|
1881
|
-
const button = event.target.closest("[data-collection]")
|
|
1882
|
-
if (!button) return
|
|
1883
|
-
state.collection = button.dataset.collection
|
|
1884
|
-
renderState().catch(showError)
|
|
1885
|
-
})
|
|
1886
|
-
$("records").addEventListener("click", (event) => {
|
|
1887
|
-
const row = event.target.closest("[data-id]")
|
|
1888
|
-
if (!row) return
|
|
1889
|
-
openEditor(row.dataset.id).catch(showError)
|
|
1890
|
-
})
|
|
1891
|
-
|
|
1892
|
-
const editor = $("editor")
|
|
1893
|
-
async function openEditor(id) {
|
|
1894
|
-
state.editing = id || null
|
|
1895
|
-
$("editor-title").textContent = id ? "Edit record" : "New record"
|
|
1896
|
-
$("editor-id").value = id || ""
|
|
1897
|
-
$("editor-id").readOnly = Boolean(id)
|
|
1898
|
-
$("editor-delete").hidden = !id
|
|
1899
|
-
if (id) {
|
|
1900
|
-
const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
1901
|
-
$("editor-value").value = JSON.stringify(record.value, null, 2)
|
|
1902
|
-
} else {
|
|
1903
|
-
$("editor-value").value = "{\\n \\n}"
|
|
1904
|
-
}
|
|
1905
|
-
editor.showModal()
|
|
1906
|
-
}
|
|
1907
|
-
$("new-record").addEventListener("click", () => openEditor(null).catch(showError))
|
|
1908
|
-
$("editor-form").addEventListener("submit", (event) => {
|
|
1909
|
-
const submitter = event.submitter
|
|
1910
|
-
if (!submitter || submitter.value !== "save") return
|
|
1911
|
-
event.preventDefault()
|
|
1912
|
-
let value
|
|
1913
|
-
try { value = JSON.parse($("editor-value").value) }
|
|
1914
|
-
catch { showError(new Error("Value is not JSON")); return }
|
|
1915
|
-
const id = $("editor-id").value.trim()
|
|
1916
|
-
const path = "/state/" + encodeURIComponent(state.collection)
|
|
1917
|
-
const request = state.editing
|
|
1918
|
-
? api("PUT", path + "/" + encodeURIComponent(id), { value })
|
|
1919
|
-
: api("POST", path, { id: id || undefined, value })
|
|
1920
|
-
request.then(() => { editor.close(); return renderState() }).catch(showError)
|
|
1921
|
-
})
|
|
1922
|
-
$("editor-delete").addEventListener("click", () => {
|
|
1923
|
-
const id = state.editing
|
|
1924
|
-
if (!id) return
|
|
1925
|
-
api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
1926
|
-
.then(() => { editor.close(); return renderState() })
|
|
1927
|
-
.catch(showError)
|
|
1928
|
-
})
|
|
1929
|
-
|
|
1930
|
-
document.querySelectorAll("[data-advance]").forEach((button) => {
|
|
1931
|
-
button.addEventListener("click", () => {
|
|
1932
|
-
api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
|
|
1933
|
-
})
|
|
1934
|
-
})
|
|
1935
|
-
$("freeze").addEventListener("click", () => {
|
|
1936
|
-
const frozen = $("freeze").dataset.frozen === "1"
|
|
1937
|
-
api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
|
|
1938
|
-
})
|
|
1939
|
-
$("clock-reset").addEventListener("click", () => {
|
|
1940
|
-
api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
|
|
1941
|
-
})
|
|
1942
|
-
$("clock-set").addEventListener("submit", (event) => {
|
|
1943
|
-
event.preventDefault()
|
|
1944
|
-
const raw = $("clock-instant").value.trim()
|
|
1945
|
-
const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
|
|
1946
|
-
api("POST", "/clock", { set }).then(renderClock).catch(showError)
|
|
1947
|
-
})
|
|
1948
|
-
|
|
1949
|
-
$("fault-form").addEventListener("submit", (event) => {
|
|
1950
|
-
event.preventDefault()
|
|
1951
|
-
const preset = $("fault-preset").value
|
|
1952
|
-
const body = preset
|
|
1953
|
-
? { preset }
|
|
1954
|
-
: {
|
|
1955
|
-
status: Number($("fault-status").value),
|
|
1956
|
-
...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
|
|
1957
|
-
...($("fault-method").value ? { method: $("fault-method").value } : {})
|
|
1958
|
-
}
|
|
1959
|
-
if (!preset && !Number.isFinite(body.status)) {
|
|
1960
|
-
showError(new Error("Status must be a number, or choose a preset"))
|
|
1961
|
-
return
|
|
1962
|
-
}
|
|
1963
|
-
api("POST", "/faults", body).then(renderFaults).catch(showError)
|
|
1964
|
-
})
|
|
1965
|
-
$("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
|
|
1966
|
-
$("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
|
|
1967
|
-
|
|
1968
|
-
$("route-list").addEventListener("click", (event) => {
|
|
1969
|
-
const button = event.target.closest("[data-route]")
|
|
1970
|
-
if (!button) return
|
|
1971
|
-
state.route = button.dataset.route
|
|
1972
|
-
$("route-title").textContent = state.route
|
|
1973
|
-
const method = state.route.split(" ")[0]
|
|
1974
|
-
$("route-body").disabled = method === "GET" || method === "DELETE"
|
|
1975
|
-
})
|
|
1976
|
-
$("route-form").addEventListener("submit", async (event) => {
|
|
1977
|
-
event.preventDefault()
|
|
1978
|
-
if (!state.route) return showError(new Error("Choose a route first"))
|
|
1979
|
-
const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
|
|
1980
|
-
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
|
|
1981
|
-
let body
|
|
1982
|
-
if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
|
|
1983
|
-
try { body = JSON.parse($("route-body").value) }
|
|
1984
|
-
catch { return showError(new Error("Body is not JSON")) }
|
|
1985
|
-
}
|
|
1986
|
-
try {
|
|
1987
|
-
const data = await api(method, concrete, body)
|
|
1988
|
-
$("route-result").textContent = JSON.stringify(data, null, 2)
|
|
1989
|
-
} catch (error) {
|
|
1990
|
-
showError(error)
|
|
1991
|
-
}
|
|
1992
|
-
})
|
|
1993
|
-
|
|
1994
|
-
function mountPanels(panels) {
|
|
1995
|
-
const nav = $("nav")
|
|
1996
|
-
const views = $("views")
|
|
1997
|
-
for (const panel of panels) {
|
|
1998
|
-
const button = document.createElement("button")
|
|
1999
|
-
button.type = "button"
|
|
2000
|
-
button.dataset.view = panel.id
|
|
2001
|
-
button.textContent = panel.title
|
|
2002
|
-
nav.append(button)
|
|
2003
|
-
const section = document.createElement("section")
|
|
2004
|
-
section.className = "view"
|
|
2005
|
-
section.id = "view-" + panel.id
|
|
2006
|
-
section.hidden = true
|
|
2007
|
-
const title = document.createElement("h2")
|
|
2008
|
-
title.textContent = panel.title
|
|
2009
|
-
const lede = document.createElement("p")
|
|
2010
|
-
lede.className = "lede"
|
|
2011
|
-
lede.textContent = panel.description || ""
|
|
2012
|
-
const body = document.createElement("div")
|
|
2013
|
-
body.className = "panel-body"
|
|
2014
|
-
body.id = "panel-" + panel.id
|
|
2015
|
-
body.innerHTML = panel.html || ""
|
|
2016
|
-
section.append(title, lede, body)
|
|
2017
|
-
views.append(section)
|
|
2018
|
-
if (panel.script) {
|
|
2019
|
-
try {
|
|
2020
|
-
const run = new Function("root", "api", panel.script)
|
|
2021
|
-
run(body, panelApi)
|
|
2022
|
-
} catch (error) {
|
|
2023
|
-
const note = document.createElement("p")
|
|
2024
|
-
note.className = "banner show"
|
|
2025
|
-
note.textContent = error instanceof Error ? error.message : String(error)
|
|
2026
|
-
body.append(note)
|
|
2027
|
-
}
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
loadNamespaces()
|
|
2033
|
-
.then(() => api("GET", "/ui/manifest"))
|
|
2034
|
-
.then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
|
|
2035
|
-
.catch(showError)
|
|
2036
|
-
})()
|
|
2037
|
-
</script>
|
|
2838
|
+
${script}
|
|
2038
2839
|
</body>
|
|
2039
2840
|
</html>`;
|
|
2040
2841
|
};
|
|
@@ -2051,10 +2852,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
|
|
|
2051
2852
|
});
|
|
2052
2853
|
var adminUiRoutes = (service, ui) => {
|
|
2053
2854
|
const shell = () => renderAdminDocument(service);
|
|
2054
|
-
const
|
|
2855
|
+
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
2055
2856
|
return {
|
|
2056
|
-
"GET /ui": () => htmlResponse(
|
|
2057
|
-
"GET /ui/": () => htmlResponse(
|
|
2857
|
+
"GET /ui": () => htmlResponse(document3()),
|
|
2858
|
+
"GET /ui/": () => htmlResponse(document3()),
|
|
2058
2859
|
"GET /ui/manifest": () => json2({
|
|
2059
2860
|
service,
|
|
2060
2861
|
panels: (ui?.panels ?? []).map((panel) => ({
|
|
@@ -2080,11 +2881,11 @@ var EXTENSION_KEYS = {
|
|
|
2080
2881
|
};
|
|
2081
2882
|
|
|
2082
2883
|
// ../../openapi/metadata/dist/read.js
|
|
2083
|
-
var
|
|
2884
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2084
2885
|
var extensionOf = (holder, key) => holder[key];
|
|
2085
2886
|
var operationMetadata = (operation) => {
|
|
2086
2887
|
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
2087
|
-
const ext =
|
|
2888
|
+
const ext = isRecord3(raw) ? raw : {};
|
|
2088
2889
|
const supported = ext.supported ?? true;
|
|
2089
2890
|
const parity = ext.parity ?? {};
|
|
2090
2891
|
return {
|
|
@@ -2120,9 +2921,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
2120
2921
|
this.name = "OperationRegistryError";
|
|
2121
2922
|
}
|
|
2122
2923
|
};
|
|
2123
|
-
var verifyOperations = (
|
|
2924
|
+
var verifyOperations = (document3, handlers) => {
|
|
2124
2925
|
const problems = [];
|
|
2125
|
-
const operations = listOperations(
|
|
2926
|
+
const operations = listOperations(document3);
|
|
2126
2927
|
const seen = /* @__PURE__ */ new Set();
|
|
2127
2928
|
for (const operation of operations) {
|
|
2128
2929
|
if (seen.has(operation.operationId))
|
|
@@ -2238,7 +3039,7 @@ var SAMPLE = 25;
|
|
|
2238
3039
|
var WALK_LIMIT = 1e4;
|
|
2239
3040
|
var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
|
|
2240
3041
|
var fail = (status, message) => ({ ok: false, status, message });
|
|
2241
|
-
var
|
|
3042
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2242
3043
|
var kindOf = (value) => {
|
|
2243
3044
|
if (value === null)
|
|
2244
3045
|
return "null";
|
|
@@ -2337,7 +3138,7 @@ var absorb = (accum, value) => {
|
|
|
2337
3138
|
const kind = kindOf(value);
|
|
2338
3139
|
accum.kinds.add(kind);
|
|
2339
3140
|
accum.present++;
|
|
2340
|
-
if (!
|
|
3141
|
+
if (!isRecord4(value))
|
|
2341
3142
|
return;
|
|
2342
3143
|
for (const [key, child] of Object.entries(value)) {
|
|
2343
3144
|
const nested = accum.nested.get(key) ?? blankAccum(key);
|
|
@@ -2362,7 +3163,7 @@ var inferFields = (records) => {
|
|
|
2362
3163
|
if (records.length === 0)
|
|
2363
3164
|
return [];
|
|
2364
3165
|
const sample = records.slice(0, SAMPLE);
|
|
2365
|
-
const objects = sample.filter((record) =>
|
|
3166
|
+
const objects = sample.filter((record) => isRecord4(record.value));
|
|
2366
3167
|
if (objects.length === 0) {
|
|
2367
3168
|
const kinds = new Set(sample.map((record) => kindOf(record.value)));
|
|
2368
3169
|
const list = [...kinds];
|
|
@@ -2549,7 +3350,7 @@ var patchStateRecord = (scope, name, id, value) => {
|
|
|
2549
3350
|
const parsed = jsonValue(value);
|
|
2550
3351
|
if (!parsed.ok)
|
|
2551
3352
|
return parsed;
|
|
2552
|
-
const next =
|
|
3353
|
+
const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
|
|
2553
3354
|
return replaceStateRecord(scope, name, id, next);
|
|
2554
3355
|
};
|
|
2555
3356
|
var deleteStateRecord = (scope, name, id) => {
|
|
@@ -2617,7 +3418,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2617
3418
|
const scope = open(namespace);
|
|
2618
3419
|
if (!scope.ok)
|
|
2619
3420
|
return fromResult(scope, 201);
|
|
2620
|
-
if (!
|
|
3421
|
+
if (!isRecord4(body) || !("value" in body)) {
|
|
2621
3422
|
return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
|
|
2622
3423
|
}
|
|
2623
3424
|
const id = body.id;
|
|
@@ -2635,7 +3436,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2635
3436
|
const scope = open(namespace);
|
|
2636
3437
|
if (!scope.ok)
|
|
2637
3438
|
return fromResult(scope, 200);
|
|
2638
|
-
if (!
|
|
3439
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2639
3440
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2640
3441
|
const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2641
3442
|
if (replaced.ok)
|
|
@@ -2646,7 +3447,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2646
3447
|
const scope = open(namespace);
|
|
2647
3448
|
if (!scope.ok)
|
|
2648
3449
|
return fromResult(scope, 200);
|
|
2649
|
-
if (!
|
|
3450
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2650
3451
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2651
3452
|
const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2652
3453
|
if (patched.ok)
|
|
@@ -2666,7 +3467,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2666
3467
|
};
|
|
2667
3468
|
|
|
2668
3469
|
// ../core/dist/version.js
|
|
2669
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3470
|
+
var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
|
|
2670
3471
|
|
|
2671
3472
|
// ../core/dist/signing.js
|
|
2672
3473
|
var encoder = new TextEncoder();
|
|
@@ -3003,9 +3804,9 @@ var createWebhookHub = (options) => {
|
|
|
3003
3804
|
};
|
|
3004
3805
|
var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3005
3806
|
var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
|
|
3006
|
-
var
|
|
3807
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3007
3808
|
var parseEndpoint = (value) => {
|
|
3008
|
-
if (!
|
|
3809
|
+
if (!isRecord5(value) || typeof value.url !== "string")
|
|
3009
3810
|
return "each endpoint needs a url";
|
|
3010
3811
|
try {
|
|
3011
3812
|
new URL(value.url);
|
|
@@ -3022,12 +3823,12 @@ var parseEndpoint = (value) => {
|
|
|
3022
3823
|
const events = value.events ?? value.enabledEvents;
|
|
3023
3824
|
if (Array.isArray(events))
|
|
3024
3825
|
endpoint.events = events.map(String);
|
|
3025
|
-
if (
|
|
3826
|
+
if (isRecord5(value.tags)) {
|
|
3026
3827
|
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
3027
3828
|
}
|
|
3028
3829
|
if (typeof value.account === "string")
|
|
3029
3830
|
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
3030
|
-
if (
|
|
3831
|
+
if (isRecord5(value.headers)) {
|
|
3031
3832
|
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
3032
3833
|
}
|
|
3033
3834
|
return endpoint;
|
|
@@ -3054,7 +3855,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3054
3855
|
return json4(200, { status: "ok" });
|
|
3055
3856
|
},
|
|
3056
3857
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
3057
|
-
if (!
|
|
3858
|
+
if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
3058
3859
|
return adminError3(400, "mode must be duplicate, reorder or drop");
|
|
3059
3860
|
}
|
|
3060
3861
|
const fault = { mode: body.mode };
|
|
@@ -3070,7 +3871,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3070
3871
|
}))
|
|
3071
3872
|
}),
|
|
3072
3873
|
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
3073
|
-
const list = Array.isArray(body) ? body :
|
|
3874
|
+
const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
|
|
3074
3875
|
if (!Array.isArray(list))
|
|
3075
3876
|
return adminError3(400, "expected [{url, secret?, events?}]");
|
|
3076
3877
|
const parsed = [];
|
|
@@ -3140,8 +3941,8 @@ var DroppedConnectionError = class extends TypeError {
|
|
|
3140
3941
|
this.name = "TypeError";
|
|
3141
3942
|
}
|
|
3142
3943
|
};
|
|
3143
|
-
var operationMatcher = (
|
|
3144
|
-
const matchers = listOperations(
|
|
3944
|
+
var operationMatcher = (document3) => {
|
|
3945
|
+
const matchers = listOperations(document3).map((operation) => ({
|
|
3145
3946
|
operationId: operation.operationId,
|
|
3146
3947
|
method: operation.method.toUpperCase(),
|
|
3147
3948
|
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
@@ -3236,10 +4037,10 @@ var createRuntime = (options) => {
|
|
|
3236
4037
|
const ensureBranch = (namespace, branch2, at) => {
|
|
3237
4038
|
if (!BRANCH_PATTERN2.test(branch2))
|
|
3238
4039
|
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
3239
|
-
const
|
|
4040
|
+
const history2 = timeline(namespace);
|
|
3240
4041
|
if (branch2 === "main") {
|
|
3241
4042
|
if (at !== void 0) {
|
|
3242
|
-
const point =
|
|
4043
|
+
const point = history2.checkout("main", at);
|
|
3243
4044
|
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
3244
4045
|
captured.set(namespace, point.value.snapshot);
|
|
3245
4046
|
rng.setState(point.value.rngState);
|
|
@@ -3252,10 +4053,10 @@ var createRuntime = (options) => {
|
|
|
3252
4053
|
return namespace;
|
|
3253
4054
|
}
|
|
3254
4055
|
const storage = physicalBranch(namespace, branch2);
|
|
3255
|
-
if (!
|
|
4056
|
+
if (!history2.hasBranch(branch2)) {
|
|
3256
4057
|
if (at === void 0)
|
|
3257
|
-
|
|
3258
|
-
const point =
|
|
4058
|
+
history2.commit(capture(namespace));
|
|
4059
|
+
const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
|
|
3259
4060
|
const branchRng = createRng(options.seed ?? 0);
|
|
3260
4061
|
if (point)
|
|
3261
4062
|
branchRng.setState(point.value.rngState);
|
|
@@ -3264,8 +4065,8 @@ var createRuntime = (options) => {
|
|
|
3264
4065
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3265
4066
|
if (point)
|
|
3266
4067
|
captured.set(storage, point.value.snapshot);
|
|
3267
|
-
} else if (at !== void 0 &&
|
|
3268
|
-
const point =
|
|
4068
|
+
} else if (at !== void 0 && history2.head(branch2)?.id !== at) {
|
|
4069
|
+
const point = history2.checkout(branch2, at);
|
|
3269
4070
|
if (!instances.has(storage)) {
|
|
3270
4071
|
const branchRng = createRng(options.seed ?? 0);
|
|
3271
4072
|
branchRng.setState(point.value.rngState);
|
|
@@ -3276,7 +4077,7 @@ var createRuntime = (options) => {
|
|
|
3276
4077
|
captured.set(storage, point.value.snapshot);
|
|
3277
4078
|
} else {
|
|
3278
4079
|
if (!instances.has(storage)) {
|
|
3279
|
-
const point =
|
|
4080
|
+
const point = history2.head(branch2);
|
|
3280
4081
|
const branchRng = createRng(options.seed ?? 0);
|
|
3281
4082
|
if (point)
|
|
3282
4083
|
branchRng.setState(point.value.rngState);
|
|
@@ -3300,8 +4101,8 @@ var createRuntime = (options) => {
|
|
|
3300
4101
|
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
3301
4102
|
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
3302
4103
|
const branchName = checkoutOptions.branch ?? "main";
|
|
3303
|
-
const
|
|
3304
|
-
const point =
|
|
4104
|
+
const history2 = timeline(namespace);
|
|
4105
|
+
const point = history2.checkout(branchName, checkpointId);
|
|
3305
4106
|
const storage = ensureBranch(namespace, branchName);
|
|
3306
4107
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3307
4108
|
captured.set(storage, point.value.snapshot);
|
|
@@ -3351,9 +4152,9 @@ var createRuntime = (options) => {
|
|
|
3351
4152
|
instance(name);
|
|
3352
4153
|
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
3353
4154
|
captured.set(name, from);
|
|
3354
|
-
const
|
|
3355
|
-
if (
|
|
3356
|
-
|
|
4155
|
+
const history2 = timelines.get(name);
|
|
4156
|
+
if (history2)
|
|
4157
|
+
history2.commit(capture(name), { branch: "main" });
|
|
3357
4158
|
else
|
|
3358
4159
|
timeline(name);
|
|
3359
4160
|
};
|
|
@@ -3582,10 +4383,10 @@ var createRuntime = (options) => {
|
|
|
3582
4383
|
},
|
|
3583
4384
|
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
3584
4385
|
inspect: (name) => {
|
|
3585
|
-
const
|
|
4386
|
+
const history2 = timeline(name);
|
|
3586
4387
|
return {
|
|
3587
|
-
branches:
|
|
3588
|
-
checkpoints:
|
|
4388
|
+
branches: history2.branches(),
|
|
4389
|
+
checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
3589
4390
|
id,
|
|
3590
4391
|
branch: branchName,
|
|
3591
4392
|
parent,
|
|
@@ -3600,11 +4401,11 @@ var createRuntime = (options) => {
|
|
|
3600
4401
|
} : {},
|
|
3601
4402
|
routes: {
|
|
3602
4403
|
...credentialRoutes(credentials),
|
|
3603
|
-
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3604
4404
|
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
3605
4405
|
...options.admin?.(runtime) ?? {},
|
|
3606
|
-
//
|
|
3607
|
-
//
|
|
4406
|
+
// Shell routes win a colliding key. Fault presets are one of them: the admin page
|
|
4407
|
+
// maps `presets`, and a service route must not replace that list with a record.
|
|
4408
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3608
4409
|
...stateAdminRoutes({
|
|
3609
4410
|
open: stateScope,
|
|
3610
4411
|
afterWrite: (namespace) => {
|
|
@@ -3679,9 +4480,12 @@ var credentialRoutes = (registry) => ({
|
|
|
3679
4480
|
}
|
|
3680
4481
|
});
|
|
3681
4482
|
var presetRoutes = (presets, runtime) => ({
|
|
3682
|
-
"GET /faults/presets": () =>
|
|
3683
|
-
|
|
3684
|
-
|
|
4483
|
+
"GET /faults/presets": () => {
|
|
4484
|
+
const body = {
|
|
4485
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
4486
|
+
};
|
|
4487
|
+
return adminJson(200, body);
|
|
4488
|
+
},
|
|
3685
4489
|
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
3686
4490
|
const name = params.name;
|
|
3687
4491
|
if (!presets[name])
|
|
@@ -3692,7 +4496,7 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
3692
4496
|
});
|
|
3693
4497
|
|
|
3694
4498
|
// src/generated/openapi.ts
|
|
3695
|
-
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Formbricks client + management API (Mockingbird subset)","description":"Stateful mock subset of open-source Formbricks (6.x): the client environment state (surveys)\\nand response creation the JS SDK uses, the v1 management API for responses and surveys\\n(\`x-api-key\`), and the widget script the web SDK loads. Errors use Formbricks' v1\\n\`{code, message, details}\` shape (\`apps/web/app/lib/api/response.ts\`). Client routes take a\\nworkspace id or a legacy (pre-5) environment id in the path.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from upstream Formbricks 6.0.0 (https://github.com/formbricks/formbricks): apps/web/app/api/v1/client/[workspaceId]/environment, apps/web/app/api/v2/client/[workspaceId]/responses, apps/web/app/api/v1/management/{responses,surveys}, and the response pipeline's webhook body (apps/web/modules/response-pipeline). Docs: https://formbricks.com/docs"}},"servers":[{"url":"https://app.formbricks.com"}],"paths":{"/api/v1/client/{workspaceId}/environment":{"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"get":{"operationId":"GetEnvironmentState","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The environment state the SDK caches (surveys, action classes, project)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["data","expiresAt"],"properties":{"data":{"type":"object","required":["surveys","actionClasses","project"],"properties":{"surveys":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}},"actionClasses":{"type":"array","items":{"type":"object"}},"project":{"type":"object"}}},"expiresAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v2/client/{workspaceId}/responses":{"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"post":{"operationId":"CreateClientResponse","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["surveyId","finished","data"],"properties":{"surveyId":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}},"finished":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ResponseData"},"contactId":{"type":["string","null"]},"displayId":{"type":["string","null"]},"singleUseId":{"type":["string","null"]},"endingId":{"type":["string","null"]},"language":{"type":"string","maxLength":10},"variables":{"type":"object"},"ttc":{"type":"object","additionalProperties":{"type":"number"}},"meta":{"type":"object","properties":{"source":{"type":"string"},"url":{"type":"string"},"action":{"type":"string"},"pagePath":{"type":"string"},"utmSource":{"type":"string"}}}}}}}},"responses":{"200":{"description":"The created response's id","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"response","identity":true}},"quotaFull":{"type":"boolean"}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/responses":{"get":{"operationId":"ListResponses","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"surveyId","in":"query","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"skip","in":"query","schema":{"type":"integer","minimum":0,"maximum":100}}],"responses":{"200":{"description":"Responses, newest first","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Response"}}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/responses/{responseId}":{"parameters":[{"name":"responseId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"response","missing":"czzzzzzzzzzzzzzzzzzzzzzzy"}}}],"get":{"operationId":"GetResponse","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"One response","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Response"}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/surveys":{"get":{"operationId":"ListSurveys","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"maximum":100}}],"responses":{"200":{"description":"Surveys, most recently updated first","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}}}}}}},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateSurvey","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspaceId","name","blocks"],"properties":{"workspaceId":{"description":"A workspace id, or (as \`environmentId\`) a legacy environment id.","type":"string","enum":["cworkspace000000000000001","cenvironment0000000000001"]},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["app","link"]},"status":{"type":"string","enum":["draft","inProgress","paused","completed"]},"blocks":{"description":"The survey's blocks of elements. The legacy \`questions\` array (one element per block) is also accepted, but not together with \`blocks\`.","type":"array","minItems":1,"maxItems":3,"items":{"type":"object","required":["id","elements"],"properties":{"id":{"type":"string","minLength":1,"maxLength":30},"name":{"type":"string","maxLength":30},"elements":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/Element"}}}}}}}}}},"responses":{"200":{"description":"The created survey","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Survey"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/surveys/{surveyId}":{"parameters":[{"name":"surveyId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}}}],"get":{"operationId":"GetSurvey","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"One survey","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Survey"}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/js/formbricks.umd.cjs":{"get":{"operationId":"GetWidgetScript","description":"The widget bundle the web SDK loads; served as a no-op script.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A no-op script defining \`window.formbricks\`","content":{"application/javascript":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"parameters":{"WorkspaceId":{"name":"workspaceId","in":"path","required":true,"description":"A workspace id, or a legacy (pre-Formbricks-5) environment id.","schema":{"type":"string","enum":["cworkspace000000000000001","cenvironment0000000000001"]}}},"responses":{"Error":{"description":"Formbricks' v1 \`{code, message, details}\` error","content":{"application/json":{"schema":{"type":"object","required":["code","message","details"],"properties":{"code":{"type":"string","enum":["not_found","gone","bad_request","internal_server_error","unauthorized","method_not_allowed","not_authenticated","forbidden","too_many_requests","conflict","payload_too_large"]},"message":{"type":"string"},"details":{"type":"object"}}}}}}},"schemas":{"ResponseData":{"type":"object","maxProperties":5,"additionalProperties":{"anyOf":[{"type":"string","maxLength":60},{"type":"number"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":30}},{"type":"object","maxProperties":3,"additionalProperties":{"type":"string","maxLength":30}}]}},"Element":{"type":"object","required":["id","type","headline"],"properties":{"id":{"type":"string","minLength":1,"maxLength":30},"type":{"type":"string","enum":["openText","multipleChoiceSingle","multipleChoiceMulti","nps","rating","date","cta","consent"]},"headline":{"type":"object","required":["default"],"properties":{"default":{"type":"string","maxLength":80}}},"required":{"type":"boolean"},"inputType":{"type":"string","enum":["text","email","url","number","phone"]}}},"Survey":{"type":"object","required":["id","name","type","status"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"survey","identity":true}},"name":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"workspaceId":{"type":"string"},"environmentId":{"description":"The legacy environment id of the survey's workspace (v1 compatibility).","type":"string"},"questions":{"type":["array","object"]},"blocks":{"type":"array"},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Response":{"type":"object","required":["id","createdAt","updatedAt","surveyId","finished","data"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"response","identity":true}},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"surveyId":{"type":"string"},"displayId":{"type":["string","null"]},"contact":{"type":["object","null"],"properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"userId":{"type":"string"}}},"contactAttributes":{"type":["object","null"]},"finished":{"type":"boolean"},"endingId":{"type":["string","null"]},"data":{"description":"An object, or (for some deployments) the same object as a JSON string.","type":["object","string"]},"variables":{"type":"object"},"ttc":{"type":"object"},"tags":{"type":"array"},"meta":{"type":"object"},"singleUseId":{"type":["string","null"]},"language":{"type":["string","null"]}}}}}}`);
|
|
4499
|
+
var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Formbricks client + management API (Mockingbird subset)","description":"Stateful mock subset of open-source Formbricks (6.x): the client environment state (surveys)\\nand response creation the JS SDK uses, the v1 management API for responses and surveys\\n(\`x-api-key\`), and the widget script the web SDK loads. Errors use Formbricks' v1\\n\`{code, message, details}\` shape (\`apps/web/app/lib/api/response.ts\`). Client routes take a\\nworkspace id or a legacy (pre-5) environment id in the path.\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from upstream Formbricks 6.0.0 (https://github.com/formbricks/formbricks): apps/web/app/api/v1/client/[workspaceId]/environment, apps/web/app/api/v2/client/[workspaceId]/responses, apps/web/app/api/v1/management/{responses,surveys}, and the response pipeline's webhook body (apps/web/modules/response-pipeline). Docs: https://formbricks.com/docs"}},"servers":[{"url":"https://app.formbricks.com"}],"paths":{"/api/v1/client/{workspaceId}/environment":{"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"get":{"operationId":"GetEnvironmentState","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The environment state the SDK caches (surveys, action classes, project)","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["data","expiresAt"],"properties":{"data":{"type":"object","required":["surveys","actionClasses","project"],"properties":{"surveys":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}},"actionClasses":{"type":"array","items":{"type":"object"}},"project":{"type":"object"}}},"expiresAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v2/client/{workspaceId}/responses":{"parameters":[{"$ref":"#/components/parameters/WorkspaceId"}],"post":{"operationId":"CreateClientResponse","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["surveyId","finished","data"],"properties":{"surveyId":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}},"finished":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ResponseData"},"contactId":{"type":["string","null"]},"displayId":{"type":["string","null"]},"singleUseId":{"type":["string","null"]},"endingId":{"type":["string","null"]},"language":{"type":"string","maxLength":10},"variables":{"type":"object"},"ttc":{"type":"object","additionalProperties":{"type":"number"}},"meta":{"type":"object","properties":{"source":{"type":"string"},"url":{"type":"string"},"action":{"type":"string"},"pagePath":{"type":"string"},"utmSource":{"type":"string"}}}}}}}},"responses":{"200":{"description":"The created response's id","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"object","properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"response","identity":true}},"quotaFull":{"type":"boolean"}}}}}}}},"400":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/responses":{"get":{"operationId":"ListResponses","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"surveyId","in":"query","schema":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"skip","in":"query","schema":{"type":"integer","minimum":0,"maximum":100}}],"responses":{"200":{"description":"Responses, newest first","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Response"}}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/responses/{responseId}":{"parameters":[{"name":"responseId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"response","missing":"czzzzzzzzzzzzzzzzzzzzzzzy"}}}],"get":{"operationId":"GetResponse","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"One response","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Response"}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/surveys":{"get":{"operationId":"ListSurveys","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"maximum":100}}],"responses":{"200":{"description":"Surveys, most recently updated first","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}}}}}}},"401":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"CreateSurvey","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["workspaceId","name","blocks"],"properties":{"workspaceId":{"description":"A workspace id, or (as \`environmentId\`) a legacy environment id.","type":"string","enum":["cworkspace000000000000001","cenvironment0000000000001"]},"name":{"type":"string","minLength":1,"maxLength":80},"type":{"type":"string","enum":["app","link"]},"status":{"type":"string","enum":["draft","inProgress","paused","completed"]},"blocks":{"description":"The survey's blocks of elements. The legacy \`questions\` array (one element per block) is also accepted, but not together with \`blocks\`.","type":"array","minItems":1,"maxItems":3,"items":{"type":"object","required":["id","elements"],"properties":{"id":{"type":"string","minLength":1,"maxLength":30},"name":{"type":"string","maxLength":30},"elements":{"type":"array","minItems":1,"maxItems":3,"items":{"$ref":"#/components/schemas/Element"}}}}}}}}}},"responses":{"200":{"description":"The created survey","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Survey"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/api/v1/management/surveys/{surveyId}":{"parameters":[{"name":"surveyId","in":"path","required":true,"schema":{"type":"string","x-mockingbird-resource-ref":{"type":"survey","missing":"czzzzzzzzzzzzzzzzzzzzzzzz"}}}],"get":{"operationId":"GetSurvey","security":[{"apiKey":[]}],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"One survey","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/Survey"}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"}}}},"/js/formbricks.umd.cjs":{"get":{"operationId":"GetWidgetScript","description":"The widget bundle the web SDK loads; served as a no-op script.","security":[],"x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A no-op script defining \`window.formbricks\`","content":{"application/javascript":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key"}},"parameters":{"WorkspaceId":{"name":"workspaceId","in":"path","required":true,"description":"A workspace id, or a legacy (pre-Formbricks-5) environment id.","schema":{"type":"string","enum":["cworkspace000000000000001","cenvironment0000000000001"]}}},"responses":{"Error":{"description":"Formbricks' v1 \`{code, message, details}\` error","content":{"application/json":{"schema":{"type":"object","required":["code","message","details"],"properties":{"code":{"type":"string","enum":["not_found","gone","bad_request","internal_server_error","unauthorized","method_not_allowed","not_authenticated","forbidden","too_many_requests","conflict","payload_too_large"]},"message":{"type":"string"},"details":{"type":"object"}}}}}}},"schemas":{"ResponseData":{"type":"object","maxProperties":5,"additionalProperties":{"anyOf":[{"type":"string","maxLength":60},{"type":"number"},{"type":"array","maxItems":3,"items":{"type":"string","maxLength":30}},{"type":"object","maxProperties":3,"additionalProperties":{"type":"string","maxLength":30}}]}},"Element":{"type":"object","required":["id","type","headline"],"properties":{"id":{"type":"string","minLength":1,"maxLength":30},"type":{"type":"string","enum":["openText","multipleChoiceSingle","multipleChoiceMulti","nps","rating","date","cta","consent"]},"headline":{"type":"object","required":["default"],"properties":{"default":{"type":"string","maxLength":80}}},"required":{"type":"boolean"},"inputType":{"type":"string","enum":["text","email","url","number","phone"]}}},"Survey":{"type":"object","required":["id","name","type","status"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"survey","identity":true}},"name":{"type":"string"},"type":{"type":"string"},"status":{"type":"string"},"workspaceId":{"type":"string"},"environmentId":{"description":"The legacy environment id of the survey's workspace (v1 compatibility).","type":"string"},"questions":{"type":["array","object"]},"blocks":{"type":"array"},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Response":{"type":"object","required":["id","createdAt","updatedAt","surveyId","finished","data"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"response","identity":true}},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"surveyId":{"type":"string"},"displayId":{"type":["string","null"]},"contact":{"type":["object","null"],"properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"userId":{"type":"string"}}},"contactAttributes":{"type":["object","null"]},"finished":{"type":"boolean"},"endingId":{"type":["string","null"]},"data":{"description":"An object, or (for some deployments) the same object as a JSON string.","type":["object","string"]},"variables":{"type":"object"},"ttc":{"type":"object"},"tags":{"type":"array"},"meta":{"type":"object"},"singleUseId":{"type":["string","null"]},"language":{"type":["string","null"]}}}}}}`);
|
|
3696
4500
|
var operationIds = ["GetEnvironmentState", "CreateClientResponse", "ListResponses", "GetResponse", "ListSurveys", "CreateSurvey", "GetSurvey", "GetWidgetScript"];
|
|
3697
4501
|
var supportedOperationIds = ["GetEnvironmentState", "CreateClientResponse", "ListResponses", "GetResponse", "ListSurveys", "CreateSurvey", "GetSurvey", "GetWidgetScript"];
|
|
3698
4502
|
|
|
@@ -4663,7 +5467,7 @@ var FORMBRICKS_PRESETS = {
|
|
|
4663
5467
|
};
|
|
4664
5468
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
4665
5469
|
var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
4666
|
-
var
|
|
5470
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4667
5471
|
var adminRoutes = (runtime) => ({
|
|
4668
5472
|
"GET /responses": ({ url, namespace }) => {
|
|
4669
5473
|
const surveyId = url.searchParams.get("surveyId");
|
|
@@ -4673,11 +5477,11 @@ var adminRoutes = (runtime) => ({
|
|
|
4673
5477
|
},
|
|
4674
5478
|
"GET /surveys": ({ namespace }) => json5(200, { surveys: runtime.instance(namespace).state.allSurveys() }),
|
|
4675
5479
|
"PUT /surveys": ({ body, namespace }) => {
|
|
4676
|
-
const list = Array.isArray(body) ? body :
|
|
5480
|
+
const list = Array.isArray(body) ? body : isRecord6(body) ? body.surveys : void 0;
|
|
4677
5481
|
if (!Array.isArray(list)) return adminError4(400, "expected [survey] or {surveys: [survey]}");
|
|
4678
5482
|
const state = runtime.instance(namespace).state;
|
|
4679
5483
|
for (const [index, survey] of list.entries()) {
|
|
4680
|
-
if (!
|
|
5484
|
+
if (!isRecord6(survey) || typeof survey.id !== "string" || typeof survey.name !== "string") {
|
|
4681
5485
|
return adminError4(400, `surveys[${index}]: id and name are required`);
|
|
4682
5486
|
}
|
|
4683
5487
|
}
|
|
@@ -4697,16 +5501,16 @@ var adminRoutes = (runtime) => ({
|
|
|
4697
5501
|
contacts: runtime.instance(namespace).state.contacts.list({ order: "oldest" }).map((row) => row.value)
|
|
4698
5502
|
}),
|
|
4699
5503
|
"PUT /contacts": ({ body, namespace }) => {
|
|
4700
|
-
const list = Array.isArray(body) ? body :
|
|
5504
|
+
const list = Array.isArray(body) ? body : isRecord6(body) ? body.contacts : void 0;
|
|
4701
5505
|
if (!Array.isArray(list)) return adminError4(400, "expected [contact] or {contacts: [contact]}");
|
|
4702
5506
|
const state = runtime.instance(namespace).state;
|
|
4703
5507
|
for (const [index, contact] of list.entries()) {
|
|
4704
|
-
if (!
|
|
5508
|
+
if (!isRecord6(contact) || typeof contact.id !== "string") {
|
|
4705
5509
|
return adminError4(400, `contacts[${index}]: id is required`);
|
|
4706
5510
|
}
|
|
4707
5511
|
}
|
|
4708
5512
|
for (const contact of list) {
|
|
4709
|
-
const attributes =
|
|
5513
|
+
const attributes = isRecord6(contact.attributes) ? Object.fromEntries(Object.entries(contact.attributes).map(([k, v]) => [k, String(v)])) : {};
|
|
4710
5514
|
if (typeof contact.userId === "string") attributes.userId = contact.userId;
|
|
4711
5515
|
state.contacts.insert(contact.id, {
|
|
4712
5516
|
id: contact.id,
|
|
@@ -4718,10 +5522,10 @@ var adminRoutes = (runtime) => ({
|
|
|
4718
5522
|
},
|
|
4719
5523
|
"GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
|
|
4720
5524
|
"PUT /settings": ({ body, namespace }) => {
|
|
4721
|
-
if (!
|
|
5525
|
+
if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
|
|
4722
5526
|
const patch = {};
|
|
4723
5527
|
if (Array.isArray(body.workspaces)) patch.workspaces = body.workspaces.map(String);
|
|
4724
|
-
if (
|
|
5528
|
+
if (isRecord6(body.legacyEnvironmentIds)) {
|
|
4725
5529
|
patch.legacyEnvironmentIds = Object.fromEntries(
|
|
4726
5530
|
Object.entries(body.legacyEnvironmentIds).map(([k, v]) => [k, String(v)])
|
|
4727
5531
|
);
|
|
@@ -4753,7 +5557,7 @@ var createRuntime2 = (options = {}) => {
|
|
|
4753
5557
|
});
|
|
4754
5558
|
const runtime = createRuntime({
|
|
4755
5559
|
name: FORMBRICKS_NAMESPACE,
|
|
4756
|
-
document,
|
|
5560
|
+
document: document2,
|
|
4757
5561
|
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
4758
5562
|
...options.clock ? { clock: options.clock } : {},
|
|
4759
5563
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
@@ -5033,7 +5837,7 @@ var FormbricksAPI = class {
|
|
|
5033
5837
|
})
|
|
5034
5838
|
});
|
|
5035
5839
|
this.service = createService({
|
|
5036
|
-
document,
|
|
5840
|
+
document: document2,
|
|
5037
5841
|
handlers,
|
|
5038
5842
|
sqlite,
|
|
5039
5843
|
namespace,
|
|
@@ -5380,7 +6184,7 @@ var FormbricksAPI = class {
|
|
|
5380
6184
|
};
|
|
5381
6185
|
|
|
5382
6186
|
export {
|
|
5383
|
-
document,
|
|
6187
|
+
document2 as document,
|
|
5384
6188
|
operationIds,
|
|
5385
6189
|
supportedOperationIds,
|
|
5386
6190
|
WORKSPACE_ID,
|
|
@@ -5396,4 +6200,4 @@ export {
|
|
|
5396
6200
|
formbricksError,
|
|
5397
6201
|
FormbricksAPI
|
|
5398
6202
|
};
|
|
5399
|
-
//# sourceMappingURL=chunk-
|
|
6203
|
+
//# sourceMappingURL=chunk-TNGXNIQB.js.map
|