@crvouga/mockingbird-service-payload-cms 1.4.0 → 1.5.1
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 +16 -0
- package/dist/{chunk-RUG4HUFE.js → chunk-GFITRH2B.js} +2 -2
- package/dist/{chunk-2ZHNDZ7B.js → chunk-XOIML4HJ.js} +1415 -611
- package/dist/chunk-XOIML4HJ.js.map +7 -0
- 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-2ZHNDZ7B.js.map +0 -7
- /package/dist/{chunk-RUG4HUFE.js.map → chunk-GFITRH2B.js.map} +0 -0
|
@@ -678,10 +678,10 @@ var OpenAPIReferenceError = class extends Error {
|
|
|
678
678
|
};
|
|
679
679
|
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
680
680
|
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
681
|
-
var resolveRef = (
|
|
681
|
+
var resolveRef = (document3, ref) => {
|
|
682
682
|
if (!ref.startsWith("#/"))
|
|
683
683
|
throw new OpenAPIReferenceError(ref);
|
|
684
|
-
let cursor =
|
|
684
|
+
let cursor = document3;
|
|
685
685
|
for (const raw of ref.slice(2).split("/")) {
|
|
686
686
|
const segment = unescapePointer(raw);
|
|
687
687
|
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
@@ -693,14 +693,14 @@ var resolveRef = (document2, ref) => {
|
|
|
693
693
|
throw new OpenAPIReferenceError(ref);
|
|
694
694
|
return cursor;
|
|
695
695
|
};
|
|
696
|
-
var deref = (
|
|
696
|
+
var deref = (document3, value) => {
|
|
697
697
|
let current = value;
|
|
698
698
|
const seen = /* @__PURE__ */ new Set();
|
|
699
699
|
while (isReference(current)) {
|
|
700
700
|
if (seen.has(current.$ref))
|
|
701
701
|
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
702
702
|
seen.add(current.$ref);
|
|
703
|
-
current = resolveRef(
|
|
703
|
+
current = resolveRef(document3, current.$ref);
|
|
704
704
|
}
|
|
705
705
|
return current;
|
|
706
706
|
};
|
|
@@ -718,36 +718,36 @@ var HTTP_METHODS = [
|
|
|
718
718
|
];
|
|
719
719
|
|
|
720
720
|
// ../../openapi/core/dist/document.js
|
|
721
|
-
var mergeParameters = (
|
|
721
|
+
var mergeParameters = (document3, item, own) => {
|
|
722
722
|
const merged = /* @__PURE__ */ new Map();
|
|
723
723
|
for (const raw of item.parameters ?? []) {
|
|
724
|
-
const parameter = deref(
|
|
724
|
+
const parameter = deref(document3, raw);
|
|
725
725
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
726
726
|
}
|
|
727
727
|
for (const raw of own ?? []) {
|
|
728
|
-
const parameter = deref(
|
|
728
|
+
const parameter = deref(document3, raw);
|
|
729
729
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
730
730
|
}
|
|
731
731
|
return [...merged.values()];
|
|
732
732
|
};
|
|
733
|
-
var listOperations = (
|
|
733
|
+
var listOperations = (document3) => {
|
|
734
734
|
const operations = [];
|
|
735
|
-
for (const [path, item] of Object.entries(
|
|
735
|
+
for (const [path, item] of Object.entries(document3.paths)) {
|
|
736
736
|
for (const method of HTTP_METHODS) {
|
|
737
737
|
const operation = item[method];
|
|
738
738
|
if (operation?.operationId === void 0)
|
|
739
739
|
continue;
|
|
740
740
|
const responses = {};
|
|
741
741
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
742
|
-
responses[status] = deref(
|
|
742
|
+
responses[status] = deref(document3, response);
|
|
743
743
|
}
|
|
744
744
|
operations.push({
|
|
745
745
|
operationId: operation.operationId,
|
|
746
746
|
method,
|
|
747
747
|
path,
|
|
748
748
|
operation,
|
|
749
|
-
parameters: mergeParameters(
|
|
750
|
-
requestBody: operation.requestBody === void 0 ? void 0 : deref(
|
|
749
|
+
parameters: mergeParameters(document3, item, operation.parameters),
|
|
750
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
|
|
751
751
|
responses
|
|
752
752
|
});
|
|
753
753
|
}
|
|
@@ -1141,6 +1141,1232 @@ var clearNamespace = (sqlite, namespace) => {
|
|
|
1141
1141
|
});
|
|
1142
1142
|
};
|
|
1143
1143
|
|
|
1144
|
+
// ../../ui/dist/reset.js
|
|
1145
|
+
var CSS_RESET = `*,
|
|
1146
|
+
*::before,
|
|
1147
|
+
*::after {
|
|
1148
|
+
box-sizing: border-box;
|
|
1149
|
+
margin: 0;
|
|
1150
|
+
padding: 0;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
* {
|
|
1154
|
+
scrollbar-width: thin;
|
|
1155
|
+
scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
*::-webkit-scrollbar {
|
|
1159
|
+
width: 12px;
|
|
1160
|
+
height: 12px;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
*::-webkit-scrollbar-track,
|
|
1164
|
+
*::-webkit-scrollbar-corner {
|
|
1165
|
+
background: Canvas;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
*::-webkit-scrollbar-thumb {
|
|
1169
|
+
background: color-mix(in srgb, CanvasText 35%, Canvas);
|
|
1170
|
+
border: 3px solid Canvas;
|
|
1171
|
+
border-radius: 999px;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
html {
|
|
1175
|
+
hanging-punctuation: first allow-end last;
|
|
1176
|
+
-webkit-text-size-adjust: none;
|
|
1177
|
+
text-size-adjust: none;
|
|
1178
|
+
tab-size: 4;
|
|
1179
|
+
font-family:
|
|
1180
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
|
|
1181
|
+
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1182
|
+
font-size: 16px;
|
|
1183
|
+
line-height: 1.5;
|
|
1184
|
+
-webkit-font-smoothing: antialiased;
|
|
1185
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1186
|
+
text-rendering: optimizeLegibility;
|
|
1187
|
+
-webkit-tap-highlight-color: transparent;
|
|
1188
|
+
scroll-behavior: smooth;
|
|
1189
|
+
background: Canvas;
|
|
1190
|
+
color: CanvasText;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
body {
|
|
1194
|
+
min-height: 100svh;
|
|
1195
|
+
line-height: 1.5;
|
|
1196
|
+
background: Canvas;
|
|
1197
|
+
color: CanvasText;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
h1 {
|
|
1201
|
+
font-size: 2em;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
h2 {
|
|
1205
|
+
font-size: 1.5em;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
h3 {
|
|
1209
|
+
font-size: 1.17em;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
h4 {
|
|
1213
|
+
font-size: 1em;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
h5 {
|
|
1217
|
+
font-size: 0.83em;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
h6 {
|
|
1221
|
+
font-size: 0.67em;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
h1,
|
|
1225
|
+
h2,
|
|
1226
|
+
h3,
|
|
1227
|
+
h4,
|
|
1228
|
+
h5,
|
|
1229
|
+
h6 {
|
|
1230
|
+
font-weight: 700;
|
|
1231
|
+
line-height: 1.2;
|
|
1232
|
+
text-wrap: balance;
|
|
1233
|
+
overflow-wrap: break-word;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
p,
|
|
1237
|
+
li,
|
|
1238
|
+
figcaption,
|
|
1239
|
+
dd,
|
|
1240
|
+
dt,
|
|
1241
|
+
blockquote {
|
|
1242
|
+
overflow-wrap: break-word;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
p,
|
|
1246
|
+
li,
|
|
1247
|
+
figcaption,
|
|
1248
|
+
blockquote {
|
|
1249
|
+
text-wrap: pretty;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
ul,
|
|
1253
|
+
ol,
|
|
1254
|
+
menu {
|
|
1255
|
+
list-style: none;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
a {
|
|
1259
|
+
color: inherit;
|
|
1260
|
+
text-decoration: underline;
|
|
1261
|
+
text-decoration-thickness: 1px;
|
|
1262
|
+
text-underline-offset: 0.15em;
|
|
1263
|
+
text-decoration-skip-ink: auto;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
b,
|
|
1267
|
+
strong {
|
|
1268
|
+
font-weight: 700;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
small {
|
|
1272
|
+
font-size: 0.875em;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
code,
|
|
1276
|
+
kbd,
|
|
1277
|
+
samp,
|
|
1278
|
+
pre {
|
|
1279
|
+
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1280
|
+
font-size: 1em;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
sub,
|
|
1284
|
+
sup {
|
|
1285
|
+
font-size: 75%;
|
|
1286
|
+
line-height: 0;
|
|
1287
|
+
position: relative;
|
|
1288
|
+
vertical-align: baseline;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
sub {
|
|
1292
|
+
bottom: -0.25em;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
sup {
|
|
1296
|
+
top: -0.5em;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
abbr[title] {
|
|
1300
|
+
text-decoration: underline dotted;
|
|
1301
|
+
text-underline-offset: 0.15em;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
mark {
|
|
1305
|
+
background: Mark;
|
|
1306
|
+
color: MarkText;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
hr {
|
|
1310
|
+
height: 0;
|
|
1311
|
+
border: 0;
|
|
1312
|
+
border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1313
|
+
color: inherit;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
img,
|
|
1317
|
+
picture,
|
|
1318
|
+
video,
|
|
1319
|
+
canvas {
|
|
1320
|
+
display: block;
|
|
1321
|
+
max-width: 100%;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
img,
|
|
1325
|
+
video {
|
|
1326
|
+
height: auto;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
svg {
|
|
1330
|
+
overflow: hidden;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
iframe {
|
|
1334
|
+
border: 0;
|
|
1335
|
+
max-width: 100%;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
table {
|
|
1339
|
+
border-collapse: collapse;
|
|
1340
|
+
border-spacing: 0;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
fieldset {
|
|
1344
|
+
border: 0;
|
|
1345
|
+
min-inline-size: 0;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
legend {
|
|
1349
|
+
padding: 0;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
button,
|
|
1353
|
+
input,
|
|
1354
|
+
select,
|
|
1355
|
+
textarea,
|
|
1356
|
+
optgroup {
|
|
1357
|
+
font: inherit;
|
|
1358
|
+
letter-spacing: inherit;
|
|
1359
|
+
color: inherit;
|
|
1360
|
+
margin: 0;
|
|
1361
|
+
vertical-align: middle;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
button,
|
|
1365
|
+
input[type="button"],
|
|
1366
|
+
input[type="submit"],
|
|
1367
|
+
input[type="reset"] {
|
|
1368
|
+
appearance: none;
|
|
1369
|
+
background: none;
|
|
1370
|
+
border: 0;
|
|
1371
|
+
padding: 0;
|
|
1372
|
+
cursor: pointer;
|
|
1373
|
+
text-align: center;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
input,
|
|
1377
|
+
textarea,
|
|
1378
|
+
select {
|
|
1379
|
+
background-color: Canvas;
|
|
1380
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1381
|
+
border-radius: 0;
|
|
1382
|
+
padding: 0.5em 0.65em;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
input[type="checkbox"],
|
|
1386
|
+
input[type="radio"],
|
|
1387
|
+
input[type="range"],
|
|
1388
|
+
input[type="file"],
|
|
1389
|
+
input[type="color"],
|
|
1390
|
+
input[type="button"],
|
|
1391
|
+
input[type="submit"],
|
|
1392
|
+
input[type="reset"],
|
|
1393
|
+
input[type="image"],
|
|
1394
|
+
input[type="hidden"] {
|
|
1395
|
+
background: none;
|
|
1396
|
+
border: 0;
|
|
1397
|
+
padding: 0;
|
|
1398
|
+
border-radius: 0;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
input[type="checkbox"],
|
|
1402
|
+
input[type="radio"] {
|
|
1403
|
+
accent-color: CanvasText;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
input[type="search"] {
|
|
1407
|
+
appearance: none;
|
|
1408
|
+
background-color: Canvas;
|
|
1409
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
input[type="search"]::-webkit-search-decoration,
|
|
1413
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
1414
|
+
input[type="search"]::-webkit-search-results-button,
|
|
1415
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
1416
|
+
-webkit-appearance: none;
|
|
1417
|
+
display: none;
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
input[type="number"] {
|
|
1421
|
+
appearance: textfield;
|
|
1422
|
+
background-color: Canvas;
|
|
1423
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
1427
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
1428
|
+
-webkit-appearance: none;
|
|
1429
|
+
margin: 0;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
textarea {
|
|
1433
|
+
resize: vertical;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
::placeholder {
|
|
1437
|
+
opacity: 1;
|
|
1438
|
+
color: color-mix(in srgb, CanvasText 45%, transparent);
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
:focus-visible {
|
|
1442
|
+
outline: 2px solid CanvasText;
|
|
1443
|
+
outline-offset: 2px;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
:focus:not(:focus-visible) {
|
|
1447
|
+
outline: none;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
:disabled,
|
|
1451
|
+
[disabled] {
|
|
1452
|
+
cursor: default;
|
|
1453
|
+
opacity: 0.6;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
label,
|
|
1457
|
+
summary,
|
|
1458
|
+
select,
|
|
1459
|
+
[role="button"] {
|
|
1460
|
+
cursor: pointer;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
summary {
|
|
1464
|
+
display: list-item;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
details {
|
|
1468
|
+
display: block;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
dialog {
|
|
1472
|
+
margin: auto;
|
|
1473
|
+
padding: 1rem;
|
|
1474
|
+
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1475
|
+
background: Canvas;
|
|
1476
|
+
color: CanvasText;
|
|
1477
|
+
max-width: calc(100vw - 2rem);
|
|
1478
|
+
max-height: calc(100svh - 2rem);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
dialog:not([open]) {
|
|
1482
|
+
display: none;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
::backdrop {
|
|
1486
|
+
background: color-mix(in srgb, CanvasText 40%, transparent);
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
[hidden] {
|
|
1490
|
+
display: none !important;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
::selection {
|
|
1494
|
+
background: Highlight;
|
|
1495
|
+
color: HighlightText;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1499
|
+
*,
|
|
1500
|
+
*::before,
|
|
1501
|
+
*::after {
|
|
1502
|
+
animation-duration: 0.01ms !important;
|
|
1503
|
+
animation-iteration-count: 1 !important;
|
|
1504
|
+
transition-duration: 0.01ms !important;
|
|
1505
|
+
scroll-behavior: auto !important;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
@media (pointer: coarse) {
|
|
1510
|
+
input,
|
|
1511
|
+
select,
|
|
1512
|
+
textarea {
|
|
1513
|
+
font-size: max(16px, 1em);
|
|
1514
|
+
}
|
|
1515
|
+
}`;
|
|
1516
|
+
|
|
1517
|
+
// ../core/dist/admin-client.js
|
|
1518
|
+
function isRecord2(value) {
|
|
1519
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1520
|
+
}
|
|
1521
|
+
function mountAdminBrand(doc, host, service, catalog) {
|
|
1522
|
+
const clip = (value, max) => {
|
|
1523
|
+
if (typeof value !== "string")
|
|
1524
|
+
return null;
|
|
1525
|
+
const trimmed = value.trim();
|
|
1526
|
+
if (trimmed === "")
|
|
1527
|
+
return null;
|
|
1528
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
1529
|
+
};
|
|
1530
|
+
const httpUrl = (value) => {
|
|
1531
|
+
if (typeof value !== "string" || value.length > 2e3)
|
|
1532
|
+
return null;
|
|
1533
|
+
try {
|
|
1534
|
+
const url = new URL(value);
|
|
1535
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1536
|
+
return null;
|
|
1537
|
+
return url.href;
|
|
1538
|
+
} catch {
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
const hexColor = (value) => {
|
|
1543
|
+
if (typeof value !== "string")
|
|
1544
|
+
return null;
|
|
1545
|
+
const raw = value.trim();
|
|
1546
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
|
|
1547
|
+
};
|
|
1548
|
+
const hostname = (value) => {
|
|
1549
|
+
try {
|
|
1550
|
+
return new URL(value).hostname.replace(/^www\./, "");
|
|
1551
|
+
} catch {
|
|
1552
|
+
return value;
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
host.replaceChildren();
|
|
1556
|
+
host.hidden = true;
|
|
1557
|
+
if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
|
|
1558
|
+
return;
|
|
1559
|
+
const row = catalog[service];
|
|
1560
|
+
if (!isRecord2(row))
|
|
1561
|
+
return;
|
|
1562
|
+
const vendor = clip(row.vendor, 80);
|
|
1563
|
+
const website = httpUrl(row.website);
|
|
1564
|
+
const guide = httpUrl(row.guide);
|
|
1565
|
+
const docs = httpUrl(row.docs);
|
|
1566
|
+
const logo = httpUrl(row.logo);
|
|
1567
|
+
const color = hexColor(row.color);
|
|
1568
|
+
const description = clip(row.description, 280);
|
|
1569
|
+
if (vendor === null || website === null && guide === null && docs === null)
|
|
1570
|
+
return;
|
|
1571
|
+
const add = (node) => {
|
|
1572
|
+
host.append(node);
|
|
1573
|
+
return node;
|
|
1574
|
+
};
|
|
1575
|
+
if (logo !== null) {
|
|
1576
|
+
const img = doc.createElement("img");
|
|
1577
|
+
img.alt = "";
|
|
1578
|
+
img.src = logo;
|
|
1579
|
+
img.width = 16;
|
|
1580
|
+
img.height = 16;
|
|
1581
|
+
img.decoding = "async";
|
|
1582
|
+
img.referrerPolicy = "no-referrer";
|
|
1583
|
+
if (color !== null)
|
|
1584
|
+
img.style.borderColor = color;
|
|
1585
|
+
add(img);
|
|
1586
|
+
}
|
|
1587
|
+
const name = doc.createElement("span");
|
|
1588
|
+
name.className = "vendor-name";
|
|
1589
|
+
name.textContent = vendor;
|
|
1590
|
+
if (description !== null)
|
|
1591
|
+
name.title = description;
|
|
1592
|
+
add(name);
|
|
1593
|
+
const sep = () => {
|
|
1594
|
+
const node = doc.createElement("span");
|
|
1595
|
+
node.className = "vendor-sep";
|
|
1596
|
+
node.setAttribute("aria-hidden", "true");
|
|
1597
|
+
node.textContent = "\xB7";
|
|
1598
|
+
return node;
|
|
1599
|
+
};
|
|
1600
|
+
const link = (href, label, title) => {
|
|
1601
|
+
const node = doc.createElement("a");
|
|
1602
|
+
node.className = "vendor-link";
|
|
1603
|
+
node.href = href;
|
|
1604
|
+
node.target = "_blank";
|
|
1605
|
+
node.rel = "noopener noreferrer";
|
|
1606
|
+
node.referrerPolicy = "no-referrer";
|
|
1607
|
+
node.textContent = label;
|
|
1608
|
+
node.title = title;
|
|
1609
|
+
return node;
|
|
1610
|
+
};
|
|
1611
|
+
if (website !== null) {
|
|
1612
|
+
add(sep());
|
|
1613
|
+
add(link(website, hostname(website), `${vendor} website`));
|
|
1614
|
+
}
|
|
1615
|
+
if (guide !== null) {
|
|
1616
|
+
add(sep());
|
|
1617
|
+
add(link(guide, "Docs", `Docs for ${vendor}`));
|
|
1618
|
+
}
|
|
1619
|
+
if (docs !== null) {
|
|
1620
|
+
add(sep());
|
|
1621
|
+
add(link(docs, "API", `${vendor} API reference`));
|
|
1622
|
+
}
|
|
1623
|
+
host.removeAttribute("hidden");
|
|
1624
|
+
host.hidden = false;
|
|
1625
|
+
}
|
|
1626
|
+
function startAdminBrand(env) {
|
|
1627
|
+
const requested = env.params.get("brands");
|
|
1628
|
+
let brandsUrl = env.brandsUrl;
|
|
1629
|
+
if (requested) {
|
|
1630
|
+
try {
|
|
1631
|
+
const url = new URL(requested, env.location.href);
|
|
1632
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
|
|
1633
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && local)
|
|
1634
|
+
brandsUrl = url.href;
|
|
1635
|
+
} catch {
|
|
1636
|
+
brandsUrl = env.brandsUrl;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const vendorHost = env.$("vendor");
|
|
1640
|
+
const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
|
|
1641
|
+
const brandService = brandRoot?.getAttribute("data-service") || "";
|
|
1642
|
+
if (vendorHost === null)
|
|
1643
|
+
return;
|
|
1644
|
+
env.fetch(brandsUrl, {
|
|
1645
|
+
headers: { accept: "application/json" },
|
|
1646
|
+
signal: AbortSignal.timeout(5e3)
|
|
1647
|
+
}).then(async (response) => {
|
|
1648
|
+
if (!response.ok)
|
|
1649
|
+
return null;
|
|
1650
|
+
const catalog = await response.json();
|
|
1651
|
+
return catalog;
|
|
1652
|
+
}).then((catalog) => {
|
|
1653
|
+
mountAdminBrand(env.document, vendorHost, brandService, catalog);
|
|
1654
|
+
}).catch(() => {
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
function faultPresetList(value) {
|
|
1658
|
+
if (!isRecord2(value))
|
|
1659
|
+
throw new Error("Fault presets response was not an object");
|
|
1660
|
+
if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
|
|
1661
|
+
return { presets: [] };
|
|
1662
|
+
}
|
|
1663
|
+
if (!Array.isArray(value.presets))
|
|
1664
|
+
throw new Error("Fault presets response was not a list");
|
|
1665
|
+
return {
|
|
1666
|
+
presets: value.presets.flatMap((item) => {
|
|
1667
|
+
if (!isRecord2(item) || typeof item.name !== "string")
|
|
1668
|
+
return [];
|
|
1669
|
+
return [{ name: item.name }];
|
|
1670
|
+
})
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
function bootAdmin(config) {
|
|
1674
|
+
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1675
|
+
const sources = ["declared", "inferred", "mixed"];
|
|
1676
|
+
const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
|
|
1677
|
+
const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
|
|
1678
|
+
const readJson2 = (text) => {
|
|
1679
|
+
const value = JSON.parse(text);
|
|
1680
|
+
return value;
|
|
1681
|
+
};
|
|
1682
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
|
|
1683
|
+
switch (char) {
|
|
1684
|
+
case "&":
|
|
1685
|
+
return "&";
|
|
1686
|
+
case "<":
|
|
1687
|
+
return "<";
|
|
1688
|
+
case ">":
|
|
1689
|
+
return ">";
|
|
1690
|
+
case '"':
|
|
1691
|
+
return """;
|
|
1692
|
+
default:
|
|
1693
|
+
return "'";
|
|
1694
|
+
}
|
|
1695
|
+
});
|
|
1696
|
+
const el = (id, kind) => {
|
|
1697
|
+
const node = document.getElementById(id);
|
|
1698
|
+
if (!(node instanceof kind))
|
|
1699
|
+
throw new Error(`admin shell is missing #${id}`);
|
|
1700
|
+
return node;
|
|
1701
|
+
};
|
|
1702
|
+
const errorMessage = (data, fallback) => {
|
|
1703
|
+
if (!isRecord2(data))
|
|
1704
|
+
return fallback;
|
|
1705
|
+
const error = data.error;
|
|
1706
|
+
if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
|
|
1707
|
+
return fallback;
|
|
1708
|
+
}
|
|
1709
|
+
return error.message;
|
|
1710
|
+
};
|
|
1711
|
+
const requireList = (value, label) => {
|
|
1712
|
+
if (value === void 0 || value === null)
|
|
1713
|
+
return [];
|
|
1714
|
+
if (!Array.isArray(value))
|
|
1715
|
+
throw new Error(`${label} was not a list`);
|
|
1716
|
+
return value;
|
|
1717
|
+
};
|
|
1718
|
+
const state = {
|
|
1719
|
+
namespace: "default",
|
|
1720
|
+
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1721
|
+
view: "overview",
|
|
1722
|
+
collection: "",
|
|
1723
|
+
shape: null,
|
|
1724
|
+
route: null,
|
|
1725
|
+
editing: null
|
|
1726
|
+
};
|
|
1727
|
+
const standard = new Set(config.standardRoutes);
|
|
1728
|
+
const banner = el("banner", HTMLElement);
|
|
1729
|
+
const keyInput = el("admin-key", HTMLInputElement);
|
|
1730
|
+
keyInput.value = state.key;
|
|
1731
|
+
const params = new URLSearchParams(location.search);
|
|
1732
|
+
const queryKey = params.get("key");
|
|
1733
|
+
if (queryKey) {
|
|
1734
|
+
state.key = queryKey;
|
|
1735
|
+
keyInput.value = state.key;
|
|
1736
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
1737
|
+
params.delete("key");
|
|
1738
|
+
const next = params.toString();
|
|
1739
|
+
history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
|
|
1740
|
+
}
|
|
1741
|
+
const queryNamespace = params.get("namespace");
|
|
1742
|
+
if (queryNamespace)
|
|
1743
|
+
state.namespace = queryNamespace;
|
|
1744
|
+
const showError = (error) => {
|
|
1745
|
+
banner.textContent = error instanceof Error ? error.message : String(error);
|
|
1746
|
+
banner.classList.add("show");
|
|
1747
|
+
};
|
|
1748
|
+
const clearError = () => {
|
|
1749
|
+
banner.classList.remove("show");
|
|
1750
|
+
};
|
|
1751
|
+
const api = async (method, path, body) => {
|
|
1752
|
+
const headers = { accept: "application/json" };
|
|
1753
|
+
if (state.key !== "")
|
|
1754
|
+
headers[config.adminKeyHeader] = state.key;
|
|
1755
|
+
if (state.namespace !== "")
|
|
1756
|
+
headers["x-mockingbird-namespace"] = state.namespace;
|
|
1757
|
+
if (body !== void 0)
|
|
1758
|
+
headers["content-type"] = "application/json";
|
|
1759
|
+
const init = { method, headers };
|
|
1760
|
+
if (body !== void 0)
|
|
1761
|
+
init.body = JSON.stringify(body);
|
|
1762
|
+
const response = await fetch(config.adminPrefix + path, init);
|
|
1763
|
+
const text = await response.text();
|
|
1764
|
+
let data = null;
|
|
1765
|
+
if (text !== "") {
|
|
1766
|
+
try {
|
|
1767
|
+
data = readJson2(text);
|
|
1768
|
+
} catch {
|
|
1769
|
+
data = { raw: text };
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
if (response.status === 401) {
|
|
1773
|
+
keyInput.focus();
|
|
1774
|
+
throw new Error(errorMessage(data, "Admin key required"));
|
|
1775
|
+
}
|
|
1776
|
+
if (!response.ok) {
|
|
1777
|
+
throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
|
|
1778
|
+
}
|
|
1779
|
+
return data;
|
|
1780
|
+
};
|
|
1781
|
+
const originGet = async (path) => {
|
|
1782
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
1783
|
+
if (!response.ok)
|
|
1784
|
+
throw new Error(`${response.status} ${path}`);
|
|
1785
|
+
const value = await response.json();
|
|
1786
|
+
return value;
|
|
1787
|
+
};
|
|
1788
|
+
const panelApi = {
|
|
1789
|
+
get: (path) => api("GET", path),
|
|
1790
|
+
send: (method, path, body) => api(method, path, body),
|
|
1791
|
+
namespace: () => state.namespace
|
|
1792
|
+
};
|
|
1793
|
+
const isPanelScript = (value) => typeof value === "function";
|
|
1794
|
+
const setTheme = (theme) => {
|
|
1795
|
+
if (theme)
|
|
1796
|
+
document.documentElement.dataset.theme = theme;
|
|
1797
|
+
else
|
|
1798
|
+
delete document.documentElement.dataset.theme;
|
|
1799
|
+
};
|
|
1800
|
+
const storedTheme = localStorage.getItem("mockingbird-admin-theme");
|
|
1801
|
+
if (storedTheme)
|
|
1802
|
+
setTheme(storedTheme);
|
|
1803
|
+
el("theme", HTMLButtonElement).addEventListener("click", () => {
|
|
1804
|
+
const current = document.documentElement.dataset.theme;
|
|
1805
|
+
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
1806
|
+
const next = dark ? "light" : "dark";
|
|
1807
|
+
localStorage.setItem("mockingbird-admin-theme", next);
|
|
1808
|
+
setTheme(next);
|
|
1809
|
+
});
|
|
1810
|
+
const show = (view) => {
|
|
1811
|
+
state.view = view;
|
|
1812
|
+
for (const button of document.querySelectorAll("#nav button")) {
|
|
1813
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1814
|
+
continue;
|
|
1815
|
+
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
|
|
1816
|
+
}
|
|
1817
|
+
for (const section of document.querySelectorAll("main .view")) {
|
|
1818
|
+
if (!(section instanceof HTMLElement))
|
|
1819
|
+
continue;
|
|
1820
|
+
section.hidden = section.id !== `view-${view}`;
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
el("nav", HTMLElement).addEventListener("click", (event) => {
|
|
1824
|
+
const target = event.target;
|
|
1825
|
+
if (!(target instanceof Element))
|
|
1826
|
+
return;
|
|
1827
|
+
const button = target.closest("button[data-view]");
|
|
1828
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1829
|
+
return;
|
|
1830
|
+
const view = button.dataset.view;
|
|
1831
|
+
if (view === void 0)
|
|
1832
|
+
return;
|
|
1833
|
+
show(view);
|
|
1834
|
+
refresh().catch(showError);
|
|
1835
|
+
});
|
|
1836
|
+
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>`;
|
|
1837
|
+
const table = (columns, rows) => {
|
|
1838
|
+
if (rows.length === 0)
|
|
1839
|
+
return '<p class="empty">Nothing here yet.</p>';
|
|
1840
|
+
const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
|
|
1841
|
+
const body = rows.map((row) => {
|
|
1842
|
+
const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
|
|
1843
|
+
const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
|
|
1844
|
+
return `<tr${attrs}>${cells}</tr>`;
|
|
1845
|
+
}).join("");
|
|
1846
|
+
return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
|
1847
|
+
};
|
|
1848
|
+
const preview = (value) => {
|
|
1849
|
+
const text = JSON.stringify(value) ?? "null";
|
|
1850
|
+
return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
|
|
1851
|
+
};
|
|
1852
|
+
const when = (epoch) => {
|
|
1853
|
+
const date = new Date(epoch);
|
|
1854
|
+
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
|
|
1855
|
+
};
|
|
1856
|
+
const clockCard = (epoch) => {
|
|
1857
|
+
const date = new Date(epoch);
|
|
1858
|
+
if (Number.isNaN(date.getTime()))
|
|
1859
|
+
return esc(String(epoch));
|
|
1860
|
+
const iso = date.toISOString();
|
|
1861
|
+
return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
|
|
1862
|
+
};
|
|
1863
|
+
const decodeClock = (value) => {
|
|
1864
|
+
if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
|
|
1865
|
+
throw new Error("Clock response was not a clock");
|
|
1866
|
+
}
|
|
1867
|
+
return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
|
|
1868
|
+
};
|
|
1869
|
+
const decodeField = (value) => {
|
|
1870
|
+
if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
|
|
1871
|
+
return void 0;
|
|
1872
|
+
}
|
|
1873
|
+
const field = {
|
|
1874
|
+
name: value.name,
|
|
1875
|
+
kind: value.kind,
|
|
1876
|
+
optional: value.optional === true
|
|
1877
|
+
};
|
|
1878
|
+
if (typeof value.description === "string")
|
|
1879
|
+
field.description = value.description;
|
|
1880
|
+
if (Array.isArray(value.fields)) {
|
|
1881
|
+
field.fields = value.fields.flatMap((item) => {
|
|
1882
|
+
const child = decodeField(item);
|
|
1883
|
+
return child === void 0 ? [] : [child];
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
return field;
|
|
1887
|
+
};
|
|
1888
|
+
const decodeCollection = (value) => {
|
|
1889
|
+
if (!isRecord2(value) || typeof value.name !== "string")
|
|
1890
|
+
return void 0;
|
|
1891
|
+
const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
|
|
1892
|
+
const field = decodeField(item);
|
|
1893
|
+
return field === void 0 ? [] : [field];
|
|
1894
|
+
}) : [];
|
|
1895
|
+
const collection = {
|
|
1896
|
+
name: value.name,
|
|
1897
|
+
label: typeof value.label === "string" ? value.label : value.name,
|
|
1898
|
+
count: typeof value.count === "number" ? value.count : 0,
|
|
1899
|
+
source: isSource(value.source) ? value.source : "inferred",
|
|
1900
|
+
fields
|
|
1901
|
+
};
|
|
1902
|
+
if (typeof value.description === "string")
|
|
1903
|
+
collection.description = value.description;
|
|
1904
|
+
return collection;
|
|
1905
|
+
};
|
|
1906
|
+
const decodeState = (value) => {
|
|
1907
|
+
if (!isRecord2(value) || !Array.isArray(value.collections)) {
|
|
1908
|
+
throw new Error("State response has no collection list");
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
|
|
1912
|
+
storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
|
|
1913
|
+
collections: value.collections.flatMap((item) => {
|
|
1914
|
+
const collection = decodeCollection(item);
|
|
1915
|
+
return collection === void 0 ? [] : [collection];
|
|
1916
|
+
})
|
|
1917
|
+
};
|
|
1918
|
+
};
|
|
1919
|
+
const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
|
|
1920
|
+
if (!isRecord2(item) || typeof item.id !== "string")
|
|
1921
|
+
return [];
|
|
1922
|
+
return [
|
|
1923
|
+
{
|
|
1924
|
+
id: item.id,
|
|
1925
|
+
seq: typeof item.seq === "number" ? item.seq : 0,
|
|
1926
|
+
value: item.value,
|
|
1927
|
+
attrs: ` class="clickable" data-id="${esc(item.id)}"`
|
|
1928
|
+
}
|
|
1929
|
+
];
|
|
1930
|
+
});
|
|
1931
|
+
const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
|
|
1932
|
+
const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
|
|
1933
|
+
if (!isRecord2(item))
|
|
1934
|
+
return [];
|
|
1935
|
+
return [
|
|
1936
|
+
{
|
|
1937
|
+
at: textOf(item.at),
|
|
1938
|
+
method: textOf(item.method),
|
|
1939
|
+
path: textOf(item.path),
|
|
1940
|
+
status: textOf(item.status),
|
|
1941
|
+
durationMs: textOf(item.durationMs),
|
|
1942
|
+
operationId: textOf(item.operationId)
|
|
1943
|
+
}
|
|
1944
|
+
];
|
|
1945
|
+
});
|
|
1946
|
+
const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
|
|
1947
|
+
if (!isRecord2(item))
|
|
1948
|
+
return [];
|
|
1949
|
+
return [
|
|
1950
|
+
{
|
|
1951
|
+
id: textOf(item.id),
|
|
1952
|
+
method: typeof item.method === "string" ? item.method : void 0,
|
|
1953
|
+
pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
|
|
1954
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0,
|
|
1955
|
+
status: typeof item.status === "number" ? item.status : void 0,
|
|
1956
|
+
effect: typeof item.effect === "string" ? item.effect : void 0,
|
|
1957
|
+
drop: typeof item.drop === "boolean" ? item.drop : void 0,
|
|
1958
|
+
latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
|
|
1959
|
+
}
|
|
1960
|
+
];
|
|
1961
|
+
});
|
|
1962
|
+
const loadNamespaces = async () => {
|
|
1963
|
+
const data = await api("GET", "/namespaces");
|
|
1964
|
+
const select = el("namespace", HTMLSelectElement);
|
|
1965
|
+
const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
|
|
1966
|
+
const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
|
|
1967
|
+
const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
|
|
1968
|
+
if (!names.includes(state.namespace))
|
|
1969
|
+
names.unshift(state.namespace);
|
|
1970
|
+
select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
|
|
1971
|
+
select.value = state.namespace;
|
|
1972
|
+
};
|
|
1973
|
+
const renderOverview = async () => {
|
|
1974
|
+
const [health, shape, clock, journal] = await Promise.all([
|
|
1975
|
+
originGet("/health"),
|
|
1976
|
+
api("GET", "/state"),
|
|
1977
|
+
api("GET", "/clock"),
|
|
1978
|
+
api("GET", "/requests?limit=8")
|
|
1979
|
+
]);
|
|
1980
|
+
if (!isRecord2(health))
|
|
1981
|
+
throw new Error("Health response was not an object");
|
|
1982
|
+
const decoded = decodeState(shape);
|
|
1983
|
+
state.shape = decoded;
|
|
1984
|
+
const time = decodeClock(clock);
|
|
1985
|
+
el("overview-cards", HTMLElement).innerHTML = cards([
|
|
1986
|
+
{
|
|
1987
|
+
k: "Service",
|
|
1988
|
+
v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
|
|
1989
|
+
},
|
|
1990
|
+
{
|
|
1991
|
+
k: "Status",
|
|
1992
|
+
v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
|
|
1993
|
+
},
|
|
1994
|
+
{ k: "Namespace", v: esc(state.namespace) },
|
|
1995
|
+
{ k: "Collections", v: String(decoded.collections.length) },
|
|
1996
|
+
{
|
|
1997
|
+
k: "Records",
|
|
1998
|
+
v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
|
|
1999
|
+
},
|
|
2000
|
+
{ k: "Clock", v: clockCard(time.now) }
|
|
2001
|
+
]);
|
|
2002
|
+
el("overview-requests", HTMLElement).innerHTML = table([
|
|
2003
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2004
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2005
|
+
{
|
|
2006
|
+
label: "Status",
|
|
2007
|
+
cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
|
|
2008
|
+
},
|
|
2009
|
+
{ label: "Operation", cell: (row) => esc(row.operationId) }
|
|
2010
|
+
], decodeJournal(journal));
|
|
2011
|
+
};
|
|
2012
|
+
const schemaChips = (collection) => {
|
|
2013
|
+
if (collection.fields.length === 0) {
|
|
2014
|
+
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
|
|
2015
|
+
}
|
|
2016
|
+
return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
|
|
2017
|
+
};
|
|
2018
|
+
const renderState = async () => {
|
|
2019
|
+
const shape = decodeState(await api("GET", "/state"));
|
|
2020
|
+
state.shape = shape;
|
|
2021
|
+
const host = el("collections", HTMLElement);
|
|
2022
|
+
if (shape.collections.length === 0) {
|
|
2023
|
+
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
|
|
2024
|
+
el("schema", HTMLElement).innerHTML = "";
|
|
2025
|
+
el("records", HTMLElement).innerHTML = "";
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2029
|
+
const first = shape.collections[0];
|
|
2030
|
+
if (first === void 0)
|
|
2031
|
+
return;
|
|
2032
|
+
state.collection = first.name;
|
|
2033
|
+
}
|
|
2034
|
+
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("");
|
|
2035
|
+
const selected = shape.collections.find((item) => item.name === state.collection);
|
|
2036
|
+
if (selected === void 0)
|
|
2037
|
+
return;
|
|
2038
|
+
const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
|
|
2039
|
+
el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
|
|
2040
|
+
const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
|
|
2041
|
+
el("records", HTMLElement).innerHTML = table([
|
|
2042
|
+
{ label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
|
|
2043
|
+
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2044
|
+
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2045
|
+
], decodeRecords(page));
|
|
2046
|
+
};
|
|
2047
|
+
const renderClock = async () => {
|
|
2048
|
+
const clock = decodeClock(await api("GET", "/clock"));
|
|
2049
|
+
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>`;
|
|
2050
|
+
const freeze = el("freeze", HTMLButtonElement);
|
|
2051
|
+
freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
|
|
2052
|
+
freeze.dataset.frozen = clock.frozen ? "1" : "0";
|
|
2053
|
+
};
|
|
2054
|
+
const renderFaults = async () => {
|
|
2055
|
+
const [faults, presets] = await Promise.all([
|
|
2056
|
+
api("GET", "/faults"),
|
|
2057
|
+
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2058
|
+
]);
|
|
2059
|
+
const select = el("fault-preset", HTMLSelectElement);
|
|
2060
|
+
const current = select.value;
|
|
2061
|
+
const list = faultPresetList(presets).presets;
|
|
2062
|
+
select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
|
|
2063
|
+
select.value = current;
|
|
2064
|
+
el("fault-list", HTMLElement).innerHTML = table([
|
|
2065
|
+
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2066
|
+
{
|
|
2067
|
+
label: "Match",
|
|
2068
|
+
cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
|
|
2069
|
+
},
|
|
2070
|
+
{
|
|
2071
|
+
label: "Effect",
|
|
2072
|
+
cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
|
|
2073
|
+
}
|
|
2074
|
+
], decodeFaults(faults));
|
|
2075
|
+
};
|
|
2076
|
+
const renderJournal = async () => {
|
|
2077
|
+
const journal = await api("GET", "/requests?limit=100");
|
|
2078
|
+
el("journal", HTMLElement).innerHTML = table([
|
|
2079
|
+
{ label: "When", cell: (row) => esc(row.at) },
|
|
2080
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2081
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2082
|
+
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2083
|
+
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2084
|
+
], decodeJournal(journal));
|
|
2085
|
+
};
|
|
2086
|
+
const renderRoutes = async () => {
|
|
2087
|
+
const data = await api("GET", "/");
|
|
2088
|
+
if (!isRecord2(data))
|
|
2089
|
+
throw new Error("Route list was not an object");
|
|
2090
|
+
const routes = requireList(data.routes, "Routes").map((route) => String(route));
|
|
2091
|
+
el("route-list", HTMLElement).innerHTML = routes.map((route) => {
|
|
2092
|
+
const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
|
|
2093
|
+
const parts = route.split(" ");
|
|
2094
|
+
const method = parts[0] ?? "";
|
|
2095
|
+
const path = parts.slice(1).join(" ");
|
|
2096
|
+
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>`;
|
|
2097
|
+
}).join("");
|
|
2098
|
+
};
|
|
2099
|
+
const renderers = {
|
|
2100
|
+
overview: renderOverview,
|
|
2101
|
+
state: renderState,
|
|
2102
|
+
clock: renderClock,
|
|
2103
|
+
faults: renderFaults,
|
|
2104
|
+
journal: renderJournal,
|
|
2105
|
+
routes: renderRoutes
|
|
2106
|
+
};
|
|
2107
|
+
async function refresh() {
|
|
2108
|
+
clearError();
|
|
2109
|
+
const render = renderers[state.view];
|
|
2110
|
+
if (render)
|
|
2111
|
+
await render();
|
|
2112
|
+
}
|
|
2113
|
+
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2114
|
+
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2115
|
+
refresh().catch(showError);
|
|
2116
|
+
});
|
|
2117
|
+
keyInput.addEventListener("change", () => {
|
|
2118
|
+
state.key = keyInput.value.trim();
|
|
2119
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2120
|
+
refresh().catch(showError);
|
|
2121
|
+
});
|
|
2122
|
+
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2123
|
+
refresh().catch(showError);
|
|
2124
|
+
});
|
|
2125
|
+
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2126
|
+
const target = event.target;
|
|
2127
|
+
if (!(target instanceof Element))
|
|
2128
|
+
return;
|
|
2129
|
+
const button = target.closest("[data-collection]");
|
|
2130
|
+
if (!(button instanceof HTMLElement))
|
|
2131
|
+
return;
|
|
2132
|
+
const collection = button.dataset.collection;
|
|
2133
|
+
if (collection === void 0)
|
|
2134
|
+
return;
|
|
2135
|
+
state.collection = collection;
|
|
2136
|
+
renderState().catch(showError);
|
|
2137
|
+
});
|
|
2138
|
+
el("records", HTMLElement).addEventListener("click", (event) => {
|
|
2139
|
+
const target = event.target;
|
|
2140
|
+
if (!(target instanceof Element))
|
|
2141
|
+
return;
|
|
2142
|
+
const row = target.closest("[data-id]");
|
|
2143
|
+
if (!(row instanceof HTMLElement))
|
|
2144
|
+
return;
|
|
2145
|
+
const id = row.dataset.id;
|
|
2146
|
+
if (id === void 0)
|
|
2147
|
+
return;
|
|
2148
|
+
openEditor(id).catch(showError);
|
|
2149
|
+
});
|
|
2150
|
+
const editor = el("editor", HTMLDialogElement);
|
|
2151
|
+
const editorId = el("editor-id", HTMLInputElement);
|
|
2152
|
+
const editorValue = el("editor-value", HTMLTextAreaElement);
|
|
2153
|
+
const editorDelete = el("editor-delete", HTMLButtonElement);
|
|
2154
|
+
async function openEditor(id) {
|
|
2155
|
+
state.editing = id;
|
|
2156
|
+
el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
|
|
2157
|
+
editorId.value = id ?? "";
|
|
2158
|
+
editorId.readOnly = id !== null;
|
|
2159
|
+
editorDelete.hidden = id === null;
|
|
2160
|
+
if (id) {
|
|
2161
|
+
const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
|
|
2162
|
+
if (!isRecord2(record))
|
|
2163
|
+
throw new Error("Record response was not an object");
|
|
2164
|
+
editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
|
|
2165
|
+
} else {
|
|
2166
|
+
editorValue.value = "{\n \n}";
|
|
2167
|
+
}
|
|
2168
|
+
editor.showModal();
|
|
2169
|
+
}
|
|
2170
|
+
el("new-record", HTMLButtonElement).addEventListener("click", () => {
|
|
2171
|
+
openEditor(null).catch(showError);
|
|
2172
|
+
});
|
|
2173
|
+
el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2174
|
+
const submitter = event.submitter;
|
|
2175
|
+
if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
|
|
2176
|
+
return;
|
|
2177
|
+
event.preventDefault();
|
|
2178
|
+
let value;
|
|
2179
|
+
try {
|
|
2180
|
+
value = readJson2(editorValue.value);
|
|
2181
|
+
} catch {
|
|
2182
|
+
showError(new Error("Value is not JSON"));
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
const id = editorId.value.trim();
|
|
2186
|
+
const path = `/state/${encodeURIComponent(state.collection)}`;
|
|
2187
|
+
const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
|
|
2188
|
+
request.then(() => {
|
|
2189
|
+
editor.close();
|
|
2190
|
+
return renderState();
|
|
2191
|
+
}).catch(showError);
|
|
2192
|
+
});
|
|
2193
|
+
editorDelete.addEventListener("click", () => {
|
|
2194
|
+
const id = state.editing;
|
|
2195
|
+
if (!id)
|
|
2196
|
+
return;
|
|
2197
|
+
api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
|
|
2198
|
+
editor.close();
|
|
2199
|
+
return renderState();
|
|
2200
|
+
}).catch(showError);
|
|
2201
|
+
});
|
|
2202
|
+
for (const button of document.querySelectorAll("[data-advance]")) {
|
|
2203
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2204
|
+
continue;
|
|
2205
|
+
button.addEventListener("click", () => {
|
|
2206
|
+
const raw = button.dataset.advance;
|
|
2207
|
+
if (raw === void 0)
|
|
2208
|
+
return;
|
|
2209
|
+
api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
|
|
2210
|
+
});
|
|
2211
|
+
}
|
|
2212
|
+
el("freeze", HTMLButtonElement).addEventListener("click", () => {
|
|
2213
|
+
const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
|
|
2214
|
+
api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
|
|
2215
|
+
});
|
|
2216
|
+
el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
|
|
2217
|
+
api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
|
|
2218
|
+
});
|
|
2219
|
+
el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2220
|
+
event.preventDefault();
|
|
2221
|
+
const raw = el("clock-instant", HTMLInputElement).value.trim();
|
|
2222
|
+
const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
|
|
2223
|
+
api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
|
|
2224
|
+
});
|
|
2225
|
+
el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2226
|
+
event.preventDefault();
|
|
2227
|
+
const preset = el("fault-preset", HTMLSelectElement).value;
|
|
2228
|
+
if (preset !== "") {
|
|
2229
|
+
api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const status = Number(el("fault-status", HTMLInputElement).value);
|
|
2233
|
+
if (!Number.isFinite(status)) {
|
|
2234
|
+
showError(new Error("Status must be a number, or choose a preset"));
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
const path = el("fault-path", HTMLInputElement).value;
|
|
2238
|
+
const method = el("fault-method", HTMLInputElement).value;
|
|
2239
|
+
api("POST", "/faults", {
|
|
2240
|
+
status,
|
|
2241
|
+
...path !== "" ? { pathPrefix: path } : {},
|
|
2242
|
+
...method !== "" ? { method } : {}
|
|
2243
|
+
}).then(() => renderFaults()).catch(showError);
|
|
2244
|
+
});
|
|
2245
|
+
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2246
|
+
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2247
|
+
});
|
|
2248
|
+
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2249
|
+
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2250
|
+
});
|
|
2251
|
+
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2252
|
+
const target = event.target;
|
|
2253
|
+
if (!(target instanceof Element))
|
|
2254
|
+
return;
|
|
2255
|
+
const button = target.closest("[data-route]");
|
|
2256
|
+
if (!(button instanceof HTMLElement))
|
|
2257
|
+
return;
|
|
2258
|
+
const route = button.dataset.route;
|
|
2259
|
+
if (route === void 0)
|
|
2260
|
+
return;
|
|
2261
|
+
state.route = route;
|
|
2262
|
+
el("route-title", HTMLElement).textContent = route;
|
|
2263
|
+
const method = route.split(" ")[0] ?? "";
|
|
2264
|
+
el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
|
|
2265
|
+
});
|
|
2266
|
+
el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2267
|
+
event.preventDefault();
|
|
2268
|
+
const send = async () => {
|
|
2269
|
+
if (state.route === null)
|
|
2270
|
+
throw new Error("Choose a route first");
|
|
2271
|
+
const parts = state.route.split(" ");
|
|
2272
|
+
const method = parts[0];
|
|
2273
|
+
const path = parts.slice(1).join(" ");
|
|
2274
|
+
if (method === void 0)
|
|
2275
|
+
throw new Error("Choose a route first");
|
|
2276
|
+
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
|
|
2277
|
+
let body;
|
|
2278
|
+
const routeBody = el("route-body", HTMLTextAreaElement);
|
|
2279
|
+
if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
|
|
2280
|
+
try {
|
|
2281
|
+
body = readJson2(routeBody.value);
|
|
2282
|
+
} catch {
|
|
2283
|
+
throw new Error("Body is not JSON");
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
const data = await api(method, concrete, body);
|
|
2287
|
+
el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
|
|
2288
|
+
};
|
|
2289
|
+
send().catch(showError);
|
|
2290
|
+
});
|
|
2291
|
+
const decodePanel = (value) => {
|
|
2292
|
+
if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
|
|
2293
|
+
return void 0;
|
|
2294
|
+
}
|
|
2295
|
+
const panel = {
|
|
2296
|
+
id: value.id,
|
|
2297
|
+
title: value.title,
|
|
2298
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
2299
|
+
html: typeof value.html === "string" ? value.html : ""
|
|
2300
|
+
};
|
|
2301
|
+
if (typeof value.script === "string")
|
|
2302
|
+
panel.script = value.script;
|
|
2303
|
+
return panel;
|
|
2304
|
+
};
|
|
2305
|
+
const mountPanels = (panels) => {
|
|
2306
|
+
const nav = el("nav", HTMLElement);
|
|
2307
|
+
const views = el("views", HTMLElement);
|
|
2308
|
+
for (const panel of panels) {
|
|
2309
|
+
const button = document.createElement("button");
|
|
2310
|
+
button.type = "button";
|
|
2311
|
+
button.dataset.view = panel.id;
|
|
2312
|
+
button.textContent = panel.title;
|
|
2313
|
+
nav.append(button);
|
|
2314
|
+
const section = document.createElement("section");
|
|
2315
|
+
section.className = "view";
|
|
2316
|
+
section.id = `view-${panel.id}`;
|
|
2317
|
+
section.hidden = true;
|
|
2318
|
+
const title = document.createElement("h2");
|
|
2319
|
+
title.textContent = panel.title;
|
|
2320
|
+
const lede = document.createElement("p");
|
|
2321
|
+
lede.className = "lede";
|
|
2322
|
+
lede.textContent = panel.description;
|
|
2323
|
+
const body = document.createElement("div");
|
|
2324
|
+
body.className = "panel-body";
|
|
2325
|
+
body.id = `panel-${panel.id}`;
|
|
2326
|
+
body.innerHTML = panel.html;
|
|
2327
|
+
section.append(title, lede, body);
|
|
2328
|
+
views.append(section);
|
|
2329
|
+
if (panel.script === void 0)
|
|
2330
|
+
continue;
|
|
2331
|
+
try {
|
|
2332
|
+
const compiled = new Function("root", "api", panel.script);
|
|
2333
|
+
if (!isPanelScript(compiled))
|
|
2334
|
+
throw new Error("panel script did not compile");
|
|
2335
|
+
compiled(body, panelApi);
|
|
2336
|
+
} catch (error) {
|
|
2337
|
+
const note = document.createElement("p");
|
|
2338
|
+
note.className = "banner show";
|
|
2339
|
+
note.textContent = error instanceof Error ? error.message : String(error);
|
|
2340
|
+
body.append(note);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
};
|
|
2344
|
+
startAdminBrand({
|
|
2345
|
+
document,
|
|
2346
|
+
$: (id) => document.getElementById(id),
|
|
2347
|
+
params,
|
|
2348
|
+
location: { href: location.href, origin: location.origin },
|
|
2349
|
+
fetch,
|
|
2350
|
+
brandsUrl: config.brandsUrl
|
|
2351
|
+
});
|
|
2352
|
+
loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
|
|
2353
|
+
const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
|
|
2354
|
+
const decoded = decodePanel(panel);
|
|
2355
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2356
|
+
});
|
|
2357
|
+
mountPanels(panels);
|
|
2358
|
+
return refresh();
|
|
2359
|
+
}).catch(showError);
|
|
2360
|
+
}
|
|
2361
|
+
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2362
|
+
var adminClientSource = (config) => {
|
|
2363
|
+
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2364
|
+
return `<script>
|
|
2365
|
+
${clientPrelude}
|
|
2366
|
+
bootAdmin(${payload});
|
|
2367
|
+
</script>`;
|
|
2368
|
+
};
|
|
2369
|
+
|
|
1144
2370
|
// ../core/dist/surface.js
|
|
1145
2371
|
var defineMock = (runtime) => runtime;
|
|
1146
2372
|
var STANDARD_ADMIN_ROUTES = [
|
|
@@ -1209,94 +2435,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
|
1209
2435
|
return "'";
|
|
1210
2436
|
}
|
|
1211
2437
|
});
|
|
2438
|
+
var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
|
|
1212
2439
|
var renderAdminDocument = (service) => {
|
|
1213
2440
|
const name = escapeHtml(service);
|
|
1214
|
-
const
|
|
2441
|
+
const script = adminClientSource({
|
|
2442
|
+
standardRoutes: STANDARD_ADMIN_ROUTES,
|
|
2443
|
+
adminPrefix: ADMIN_PREFIX,
|
|
2444
|
+
adminKeyHeader: ADMIN_KEY_HEADER,
|
|
2445
|
+
brandsUrl: ADMIN_BRANDS_URL,
|
|
2446
|
+
service
|
|
2447
|
+
});
|
|
1215
2448
|
return `<!DOCTYPE html>
|
|
1216
2449
|
<html lang="en">
|
|
1217
2450
|
<head>
|
|
1218
2451
|
<meta charset="utf-8">
|
|
1219
2452
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1220
2453
|
<meta name="color-scheme" content="light dark">
|
|
1221
|
-
<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='
|
|
2454
|
+
<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">
|
|
1222
2455
|
<title>${name} admin</title>
|
|
1223
2456
|
<style>
|
|
2457
|
+
${CSS_RESET}
|
|
1224
2458
|
:root {
|
|
1225
2459
|
color-scheme: light;
|
|
1226
|
-
--bg: #
|
|
2460
|
+
--bg: #fafafa;
|
|
1227
2461
|
--bg-elev: #ffffff;
|
|
1228
|
-
--bg-muted: #
|
|
1229
|
-
--bg-inset: #
|
|
1230
|
-
--line: #
|
|
1231
|
-
--line-strong: #
|
|
1232
|
-
--ink: #
|
|
1233
|
-
--muted: #
|
|
1234
|
-
--faint: #
|
|
1235
|
-
--accent: #
|
|
1236
|
-
--accent-ink: #
|
|
1237
|
-
--accent-soft: #
|
|
1238
|
-
--ok: #
|
|
1239
|
-
--ok-soft: #
|
|
1240
|
-
--warn: #
|
|
1241
|
-
--warn-soft: #
|
|
1242
|
-
--err: #
|
|
1243
|
-
--err-soft: #
|
|
1244
|
-
--shadow:
|
|
1245
|
-
--sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2462
|
+
--bg-muted: #f4f4f5;
|
|
2463
|
+
--bg-inset: #f4f4f5;
|
|
2464
|
+
--line: #dedee3;
|
|
2465
|
+
--line-strong: #c4c4cc;
|
|
2466
|
+
--ink: #18181b;
|
|
2467
|
+
--muted: #62626b;
|
|
2468
|
+
--faint: #71717a;
|
|
2469
|
+
--accent: #27272a;
|
|
2470
|
+
--accent-ink: #ffffff;
|
|
2471
|
+
--accent-soft: #f4f4f5;
|
|
2472
|
+
--ok: #166534;
|
|
2473
|
+
--ok-soft: #f0fdf4;
|
|
2474
|
+
--warn: #a16207;
|
|
2475
|
+
--warn-soft: #fefce8;
|
|
2476
|
+
--err: #a82d32;
|
|
2477
|
+
--err-soft: #fef2f2;
|
|
2478
|
+
--shadow: none;
|
|
2479
|
+
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1246
2480
|
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1247
|
-
--radius:
|
|
1248
|
-
--header:
|
|
1249
|
-
--nav:
|
|
2481
|
+
--radius: 10px;
|
|
2482
|
+
--header: 56px;
|
|
2483
|
+
--nav: 200px;
|
|
1250
2484
|
}
|
|
1251
2485
|
:root[data-theme="dark"] {
|
|
1252
2486
|
color-scheme: dark;
|
|
1253
|
-
--bg: #
|
|
1254
|
-
--bg-elev: #
|
|
1255
|
-
--bg-muted: #
|
|
1256
|
-
--bg-inset: #
|
|
1257
|
-
--line: #
|
|
1258
|
-
--line-strong: #
|
|
1259
|
-
--ink: #
|
|
1260
|
-
--muted: #
|
|
1261
|
-
--faint: #
|
|
1262
|
-
--accent: #
|
|
1263
|
-
--accent-ink: #
|
|
1264
|
-
--accent-soft: #
|
|
1265
|
-
--ok: #
|
|
1266
|
-
--ok-soft: #
|
|
1267
|
-
--warn: #
|
|
1268
|
-
--warn-soft: #
|
|
1269
|
-
--err: #
|
|
1270
|
-
--err-soft: #
|
|
1271
|
-
--shadow:
|
|
2487
|
+
--bg: #111113;
|
|
2488
|
+
--bg-elev: #19191c;
|
|
2489
|
+
--bg-muted: #242428;
|
|
2490
|
+
--bg-inset: #111113;
|
|
2491
|
+
--line: #36363c;
|
|
2492
|
+
--line-strong: #52525b;
|
|
2493
|
+
--ink: #f4f4f5;
|
|
2494
|
+
--muted: #a9a9b2;
|
|
2495
|
+
--faint: #71717a;
|
|
2496
|
+
--accent: #e4e4e7;
|
|
2497
|
+
--accent-ink: #18181b;
|
|
2498
|
+
--accent-soft: #242428;
|
|
2499
|
+
--ok: #86efac;
|
|
2500
|
+
--ok-soft: #14241b;
|
|
2501
|
+
--warn: #fde68a;
|
|
2502
|
+
--warn-soft: #2a2410;
|
|
2503
|
+
--err: #ffaaaa;
|
|
2504
|
+
--err-soft: #2c1516;
|
|
2505
|
+
--shadow: none;
|
|
1272
2506
|
}
|
|
1273
2507
|
@media (prefers-color-scheme: dark) {
|
|
1274
2508
|
:root:not([data-theme="light"]) {
|
|
1275
2509
|
color-scheme: dark;
|
|
1276
|
-
--bg: #
|
|
1277
|
-
--bg-elev: #
|
|
1278
|
-
--bg-muted: #
|
|
1279
|
-
--bg-inset: #
|
|
1280
|
-
--line: #
|
|
1281
|
-
--line-strong: #
|
|
1282
|
-
--ink: #
|
|
1283
|
-
--muted: #
|
|
1284
|
-
--faint: #
|
|
1285
|
-
--accent: #
|
|
1286
|
-
--accent-ink: #
|
|
1287
|
-
--accent-soft: #
|
|
1288
|
-
--ok: #
|
|
1289
|
-
--ok-soft: #
|
|
1290
|
-
--warn: #
|
|
1291
|
-
--warn-soft: #
|
|
1292
|
-
--err: #
|
|
1293
|
-
--err-soft: #
|
|
1294
|
-
--shadow:
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
html
|
|
2510
|
+
--bg: #111113;
|
|
2511
|
+
--bg-elev: #19191c;
|
|
2512
|
+
--bg-muted: #242428;
|
|
2513
|
+
--bg-inset: #111113;
|
|
2514
|
+
--line: #36363c;
|
|
2515
|
+
--line-strong: #52525b;
|
|
2516
|
+
--ink: #f4f4f5;
|
|
2517
|
+
--muted: #a9a9b2;
|
|
2518
|
+
--faint: #71717a;
|
|
2519
|
+
--accent: #e4e4e7;
|
|
2520
|
+
--accent-ink: #18181b;
|
|
2521
|
+
--accent-soft: #242428;
|
|
2522
|
+
--ok: #86efac;
|
|
2523
|
+
--ok-soft: #14241b;
|
|
2524
|
+
--warn: #fde68a;
|
|
2525
|
+
--warn-soft: #2a2410;
|
|
2526
|
+
--err: #ffaaaa;
|
|
2527
|
+
--err-soft: #2c1516;
|
|
2528
|
+
--shadow: none;
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
/* Fill the viewport, then grow with the content so the background covers the scroll. */
|
|
2532
|
+
html { height: 100%; }
|
|
1299
2533
|
body {
|
|
2534
|
+
margin: 0;
|
|
2535
|
+
min-height: 100%;
|
|
2536
|
+
display: flex;
|
|
2537
|
+
flex-direction: column;
|
|
1300
2538
|
font-family: var(--sans);
|
|
1301
2539
|
background: var(--bg);
|
|
1302
2540
|
color: var(--ink);
|
|
@@ -1305,24 +2543,37 @@ var renderAdminDocument = (service) => {
|
|
|
1305
2543
|
padding-right: env(safe-area-inset-right);
|
|
1306
2544
|
-webkit-text-size-adjust: 100%;
|
|
1307
2545
|
}
|
|
1308
|
-
button, input, select, textarea { font: inherit; color: inherit; }
|
|
1309
|
-
button { cursor: pointer; }
|
|
1310
2546
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1311
|
-
|
|
1312
|
-
* { scroll-behavior: auto !important; transition: none !important; }
|
|
1313
|
-
}
|
|
1314
|
-
.app { min-height: 100%; display: flex; flex-direction: column; }
|
|
2547
|
+
.app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
|
|
1315
2548
|
header.top {
|
|
1316
2549
|
position: sticky; top: 0; z-index: 5;
|
|
1317
2550
|
display: flex; align-items: center; gap: 12px;
|
|
1318
2551
|
min-height: var(--header); padding: 10px 16px;
|
|
1319
|
-
background:
|
|
2552
|
+
background: var(--bg-elev);
|
|
1320
2553
|
border-bottom: 1px solid var(--line);
|
|
1321
|
-
|
|
1322
|
-
}
|
|
1323
|
-
.brand {
|
|
1324
|
-
|
|
1325
|
-
|
|
2554
|
+
min-width: 0; max-width: 100%;
|
|
2555
|
+
}
|
|
2556
|
+
.brand {
|
|
2557
|
+
display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
|
|
2558
|
+
min-width: 0; max-width: 100%; margin-right: auto;
|
|
2559
|
+
}
|
|
2560
|
+
.brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
|
|
2561
|
+
.brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
|
|
2562
|
+
.brand-id span { color: var(--faint); font-size: 12px; }
|
|
2563
|
+
.vendor {
|
|
2564
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
2565
|
+
min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
|
|
2566
|
+
}
|
|
2567
|
+
.vendor[hidden] { display: none; }
|
|
2568
|
+
/* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
|
|
2569
|
+
.vendor img {
|
|
2570
|
+
width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
|
|
2571
|
+
border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
|
|
2572
|
+
}
|
|
2573
|
+
.vendor-name { color: var(--ink); font-weight: 600; }
|
|
2574
|
+
.vendor-sep { color: var(--faint); }
|
|
2575
|
+
.vendor-link { color: inherit; text-decoration: none; }
|
|
2576
|
+
.vendor-link:hover { color: var(--ink); text-decoration: underline; }
|
|
1326
2577
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1327
2578
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
1328
2579
|
label.field input, label.field select, .controls input[type="password"] {
|
|
@@ -1340,7 +2591,7 @@ var renderAdminDocument = (service) => {
|
|
|
1340
2591
|
record or the full nav row would widen the page past the viewport. */
|
|
1341
2592
|
.layout {
|
|
1342
2593
|
display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
|
|
1343
|
-
flex: 1
|
|
2594
|
+
flex: 1 0 auto; min-width: 0;
|
|
1344
2595
|
}
|
|
1345
2596
|
nav.side {
|
|
1346
2597
|
border-right: 1px solid var(--line); padding: 12px;
|
|
@@ -1355,7 +2606,7 @@ var renderAdminDocument = (service) => {
|
|
|
1355
2606
|
main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
|
|
1356
2607
|
.view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
|
|
1357
2608
|
.lede { margin: 0 0 16px; color: var(--muted); }
|
|
1358
|
-
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
|
|
2609
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); gap: 12px; }
|
|
1359
2610
|
.card {
|
|
1360
2611
|
background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
|
|
1361
2612
|
padding: 14px 16px; box-shadow: var(--shadow);
|
|
@@ -1394,8 +2645,8 @@ var renderAdminDocument = (service) => {
|
|
|
1394
2645
|
.banner.show { display: block; }
|
|
1395
2646
|
.empty { color: var(--muted); padding: 24px 8px; }
|
|
1396
2647
|
dialog {
|
|
1397
|
-
border: 1px solid var(--line); border-radius:
|
|
1398
|
-
color: var(--ink); width: min(640px, calc(
|
|
2648
|
+
border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
|
|
2649
|
+
color: var(--ink); width: min(640px, calc(100% - 24px)); max-width: calc(100vw - 24px); box-shadow: none;
|
|
1399
2650
|
}
|
|
1400
2651
|
dialog::backdrop { background: rgb(20 19 17 / 0.45); }
|
|
1401
2652
|
dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1422,7 +2673,7 @@ var renderAdminDocument = (service) => {
|
|
|
1422
2673
|
padding-top: max(10px, env(safe-area-inset-top));
|
|
1423
2674
|
}
|
|
1424
2675
|
input, select, textarea { font-size: 16px; }
|
|
1425
|
-
.controls { width: 100%; }
|
|
2676
|
+
.brand, .vendor, .controls { width: 100%; }
|
|
1426
2677
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
1427
2678
|
.layout { grid-template-columns: minmax(0, 1fr); }
|
|
1428
2679
|
nav.side {
|
|
@@ -1453,8 +2704,11 @@ var renderAdminDocument = (service) => {
|
|
|
1453
2704
|
<div class="app" data-service="${name}" data-mockingbird-admin>
|
|
1454
2705
|
<header class="top">
|
|
1455
2706
|
<div class="brand">
|
|
1456
|
-
<
|
|
1457
|
-
|
|
2707
|
+
<div class="brand-id">
|
|
2708
|
+
<strong>${name}</strong>
|
|
2709
|
+
<span>Admin</span>
|
|
2710
|
+
</div>
|
|
2711
|
+
<div class="vendor" id="vendor" hidden></div>
|
|
1458
2712
|
</div>
|
|
1459
2713
|
<div class="controls">
|
|
1460
2714
|
<label class="field">Namespace
|
|
@@ -1584,460 +2838,7 @@ var renderAdminDocument = (service) => {
|
|
|
1584
2838
|
</div>
|
|
1585
2839
|
</form>
|
|
1586
2840
|
</dialog>
|
|
1587
|
-
|
|
1588
|
-
(() => {
|
|
1589
|
-
const STANDARD = new Set(${standard})
|
|
1590
|
-
const state = {
|
|
1591
|
-
namespace: "default",
|
|
1592
|
-
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1593
|
-
view: "overview",
|
|
1594
|
-
collection: "",
|
|
1595
|
-
shape: null,
|
|
1596
|
-
route: null,
|
|
1597
|
-
editing: null
|
|
1598
|
-
}
|
|
1599
|
-
const $ = (id) => document.getElementById(id)
|
|
1600
|
-
const banner = $("banner")
|
|
1601
|
-
const keyInput = $("admin-key")
|
|
1602
|
-
keyInput.value = state.key
|
|
1603
|
-
const params = new URLSearchParams(location.search)
|
|
1604
|
-
if (params.get("key")) {
|
|
1605
|
-
state.key = params.get("key")
|
|
1606
|
-
keyInput.value = state.key
|
|
1607
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1608
|
-
params.delete("key")
|
|
1609
|
-
const next = params.toString()
|
|
1610
|
-
history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
|
|
1611
|
-
}
|
|
1612
|
-
if (params.get("namespace")) state.namespace = params.get("namespace")
|
|
1613
|
-
|
|
1614
|
-
const showError = (error) => {
|
|
1615
|
-
banner.textContent = error instanceof Error ? error.message : String(error)
|
|
1616
|
-
banner.classList.add("show")
|
|
1617
|
-
}
|
|
1618
|
-
const clearError = () => banner.classList.remove("show")
|
|
1619
|
-
|
|
1620
|
-
async function originGet(path) {
|
|
1621
|
-
const response = await fetch(path, { headers: { accept: "application/json" } })
|
|
1622
|
-
if (!response.ok) throw new Error(response.status + " " + path)
|
|
1623
|
-
return response.json()
|
|
1624
|
-
}
|
|
1625
|
-
async function api(method, path, body) {
|
|
1626
|
-
const headers = { accept: "application/json" }
|
|
1627
|
-
if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
|
|
1628
|
-
if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
|
|
1629
|
-
if (body !== undefined) headers["content-type"] = "application/json"
|
|
1630
|
-
const response = await fetch("${ADMIN_PREFIX}" + path, {
|
|
1631
|
-
method,
|
|
1632
|
-
headers,
|
|
1633
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1634
|
-
})
|
|
1635
|
-
const text = await response.text()
|
|
1636
|
-
let data = null
|
|
1637
|
-
if (text) {
|
|
1638
|
-
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
|
1639
|
-
}
|
|
1640
|
-
if (response.status === 401) {
|
|
1641
|
-
keyInput.focus()
|
|
1642
|
-
throw new Error((data && data.error && data.error.message) || "Admin key required")
|
|
1643
|
-
}
|
|
1644
|
-
if (!response.ok) {
|
|
1645
|
-
throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
|
|
1646
|
-
}
|
|
1647
|
-
return data
|
|
1648
|
-
}
|
|
1649
|
-
const panelApi = {
|
|
1650
|
-
get: (path) => api("GET", path),
|
|
1651
|
-
send: (method, path, body) => api(method, path, body),
|
|
1652
|
-
namespace: () => state.namespace
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
function setTheme(theme) {
|
|
1656
|
-
if (theme) document.documentElement.dataset.theme = theme
|
|
1657
|
-
else delete document.documentElement.dataset.theme
|
|
1658
|
-
}
|
|
1659
|
-
const storedTheme = localStorage.getItem("mockingbird-admin-theme")
|
|
1660
|
-
if (storedTheme) setTheme(storedTheme)
|
|
1661
|
-
$("theme").addEventListener("click", () => {
|
|
1662
|
-
const current = document.documentElement.dataset.theme
|
|
1663
|
-
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
|
|
1664
|
-
const next = dark ? "light" : "dark"
|
|
1665
|
-
localStorage.setItem("mockingbird-admin-theme", next)
|
|
1666
|
-
setTheme(next)
|
|
1667
|
-
})
|
|
1668
|
-
|
|
1669
|
-
function show(view) {
|
|
1670
|
-
state.view = view
|
|
1671
|
-
for (const button of document.querySelectorAll("#nav button")) {
|
|
1672
|
-
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
|
|
1673
|
-
}
|
|
1674
|
-
for (const section of document.querySelectorAll("main .view")) {
|
|
1675
|
-
section.hidden = section.id !== "view-" + view
|
|
1676
|
-
}
|
|
1677
|
-
}
|
|
1678
|
-
document.getElementById("nav").addEventListener("click", (event) => {
|
|
1679
|
-
const button = event.target.closest("button[data-view]")
|
|
1680
|
-
if (!button) return
|
|
1681
|
-
show(button.dataset.view)
|
|
1682
|
-
refresh().catch(showError)
|
|
1683
|
-
})
|
|
1684
|
-
|
|
1685
|
-
function cards(items) {
|
|
1686
|
-
return '<div class="grid">' + items.map((item) =>
|
|
1687
|
-
'<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
|
|
1688
|
-
).join("") + "</div>"
|
|
1689
|
-
}
|
|
1690
|
-
function table(columns, rows) {
|
|
1691
|
-
if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
|
|
1692
|
-
const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
|
|
1693
|
-
const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
|
|
1694
|
-
'<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
|
|
1695
|
-
).join("") + "</tr>").join("")
|
|
1696
|
-
return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
|
|
1697
|
-
}
|
|
1698
|
-
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
1699
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
1700
|
-
}[char]))
|
|
1701
|
-
const preview = (value) => {
|
|
1702
|
-
const text = JSON.stringify(value)
|
|
1703
|
-
return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
|
|
1704
|
-
}
|
|
1705
|
-
const when = (epoch) => {
|
|
1706
|
-
const date = new Date(epoch)
|
|
1707
|
-
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
|
|
1708
|
-
}
|
|
1709
|
-
const clockCard = (epoch) => {
|
|
1710
|
-
const date = new Date(epoch)
|
|
1711
|
-
if (Number.isNaN(date.getTime())) return esc(String(epoch))
|
|
1712
|
-
const iso = date.toISOString()
|
|
1713
|
-
return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
async function loadNamespaces() {
|
|
1717
|
-
const data = await api("GET", "/namespaces")
|
|
1718
|
-
const select = $("namespace")
|
|
1719
|
-
const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
|
|
1720
|
-
if (!names.includes(state.namespace)) names.unshift(state.namespace)
|
|
1721
|
-
select.innerHTML = names.map((name) =>
|
|
1722
|
-
'<option value="' + esc(name) + '">' + esc(name) + "</option>"
|
|
1723
|
-
).join("")
|
|
1724
|
-
select.value = state.namespace
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
async function renderOverview() {
|
|
1728
|
-
const [health, shape, clock, journal] = await Promise.all([
|
|
1729
|
-
originGet("/health"),
|
|
1730
|
-
api("GET", "/state"),
|
|
1731
|
-
api("GET", "/clock"),
|
|
1732
|
-
api("GET", "/requests?limit=8")
|
|
1733
|
-
])
|
|
1734
|
-
state.shape = shape
|
|
1735
|
-
$("overview-cards").innerHTML = cards([
|
|
1736
|
-
{ k: "Service", v: esc(health.service || "${name}") },
|
|
1737
|
-
{ k: "Status", v: esc(health.status || "ok") },
|
|
1738
|
-
{ k: "Namespace", v: esc(state.namespace) },
|
|
1739
|
-
{ k: "Collections", v: String(shape.collections.length) },
|
|
1740
|
-
{ k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
|
|
1741
|
-
{ k: "Clock", v: clockCard(clock.now) }
|
|
1742
|
-
])
|
|
1743
|
-
const requests = (journal && journal.requests) || []
|
|
1744
|
-
$("overview-requests").innerHTML = table(
|
|
1745
|
-
[
|
|
1746
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1747
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1748
|
-
{ label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
|
|
1749
|
-
{ label: "Operation", cell: (row) => esc(row.operationId || "") }
|
|
1750
|
-
],
|
|
1751
|
-
requests
|
|
1752
|
-
)
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
function schemaChips(collection) {
|
|
1756
|
-
if (!collection || collection.fields.length === 0) {
|
|
1757
|
-
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
|
|
1758
|
-
}
|
|
1759
|
-
return '<div class="chips">' + collection.fields.map((field) =>
|
|
1760
|
-
'<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
|
|
1761
|
-
).join("") + "</div>"
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
async function renderState() {
|
|
1765
|
-
const shape = await api("GET", "/state")
|
|
1766
|
-
state.shape = shape
|
|
1767
|
-
const host = $("collections")
|
|
1768
|
-
if (shape.collections.length === 0) {
|
|
1769
|
-
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
|
|
1770
|
-
$("schema").innerHTML = ""
|
|
1771
|
-
$("records").innerHTML = ""
|
|
1772
|
-
return
|
|
1773
|
-
}
|
|
1774
|
-
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
1775
|
-
state.collection = shape.collections[0].name
|
|
1776
|
-
}
|
|
1777
|
-
host.innerHTML = shape.collections.map((item) =>
|
|
1778
|
-
'<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
|
|
1779
|
-
(item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
|
|
1780
|
-
'</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
|
|
1781
|
-
).join("")
|
|
1782
|
-
const selected = shape.collections.find((item) => item.name === state.collection)
|
|
1783
|
-
$("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
|
|
1784
|
-
(selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
|
|
1785
|
-
schemaChips(selected)
|
|
1786
|
-
const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
|
|
1787
|
-
const rows = page.records.map((record) => ({
|
|
1788
|
-
...record,
|
|
1789
|
-
attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
|
|
1790
|
-
}))
|
|
1791
|
-
$("records").innerHTML = table(
|
|
1792
|
-
[
|
|
1793
|
-
{ label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
|
|
1794
|
-
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
1795
|
-
{ label: "Value", cell: (row) => preview(row.value) }
|
|
1796
|
-
],
|
|
1797
|
-
rows
|
|
1798
|
-
)
|
|
1799
|
-
}
|
|
1800
|
-
|
|
1801
|
-
async function renderClock() {
|
|
1802
|
-
const clock = await api("GET", "/clock")
|
|
1803
|
-
$("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
|
|
1804
|
-
"</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
|
|
1805
|
-
(clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
|
|
1806
|
-
$("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
|
|
1807
|
-
$("freeze").dataset.frozen = clock.frozen ? "1" : "0"
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
async function renderFaults() {
|
|
1811
|
-
const [faults, presets] = await Promise.all([
|
|
1812
|
-
api("GET", "/faults"),
|
|
1813
|
-
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
1814
|
-
])
|
|
1815
|
-
const select = $("fault-preset")
|
|
1816
|
-
const current = select.value
|
|
1817
|
-
const list = presets.presets || []
|
|
1818
|
-
select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
|
|
1819
|
-
'<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
|
|
1820
|
-
).join("")
|
|
1821
|
-
select.value = current
|
|
1822
|
-
const rules = faults.faults || []
|
|
1823
|
-
$("fault-list").innerHTML = table(
|
|
1824
|
-
[
|
|
1825
|
-
{ label: "Id", cell: (row) => esc(row.id) },
|
|
1826
|
-
{ label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
|
|
1827
|
-
{ label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
|
|
1828
|
-
],
|
|
1829
|
-
rules
|
|
1830
|
-
)
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
async function renderJournal() {
|
|
1834
|
-
const journal = await api("GET", "/requests?limit=100")
|
|
1835
|
-
$("journal").innerHTML = table(
|
|
1836
|
-
[
|
|
1837
|
-
{ label: "When", cell: (row) => esc(row.at || "") },
|
|
1838
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1839
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1840
|
-
{ label: "Status", cell: (row) => esc(row.status) },
|
|
1841
|
-
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
1842
|
-
],
|
|
1843
|
-
journal.requests || []
|
|
1844
|
-
)
|
|
1845
|
-
}
|
|
1846
|
-
|
|
1847
|
-
async function renderRoutes() {
|
|
1848
|
-
const data = await api("GET", "/")
|
|
1849
|
-
const routes = data.routes || []
|
|
1850
|
-
$("route-list").innerHTML = routes.map((route) => {
|
|
1851
|
-
const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
|
|
1852
|
-
return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
|
|
1853
|
-
'</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
|
|
1854
|
-
'</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
|
|
1855
|
-
}).join("")
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
const renderers = {
|
|
1859
|
-
overview: renderOverview,
|
|
1860
|
-
state: renderState,
|
|
1861
|
-
clock: renderClock,
|
|
1862
|
-
faults: renderFaults,
|
|
1863
|
-
journal: renderJournal,
|
|
1864
|
-
routes: renderRoutes
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
async function refresh() {
|
|
1868
|
-
clearError()
|
|
1869
|
-
if (state.view in renderers) await renderers[state.view]()
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
$("namespace").addEventListener("change", () => {
|
|
1873
|
-
state.namespace = $("namespace").value
|
|
1874
|
-
refresh().catch(showError)
|
|
1875
|
-
})
|
|
1876
|
-
keyInput.addEventListener("change", () => {
|
|
1877
|
-
state.key = keyInput.value.trim()
|
|
1878
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1879
|
-
refresh().catch(showError)
|
|
1880
|
-
})
|
|
1881
|
-
$("refresh").addEventListener("click", () => refresh().catch(showError))
|
|
1882
|
-
|
|
1883
|
-
$("collections").addEventListener("click", (event) => {
|
|
1884
|
-
const button = event.target.closest("[data-collection]")
|
|
1885
|
-
if (!button) return
|
|
1886
|
-
state.collection = button.dataset.collection
|
|
1887
|
-
renderState().catch(showError)
|
|
1888
|
-
})
|
|
1889
|
-
$("records").addEventListener("click", (event) => {
|
|
1890
|
-
const row = event.target.closest("[data-id]")
|
|
1891
|
-
if (!row) return
|
|
1892
|
-
openEditor(row.dataset.id).catch(showError)
|
|
1893
|
-
})
|
|
1894
|
-
|
|
1895
|
-
const editor = $("editor")
|
|
1896
|
-
async function openEditor(id) {
|
|
1897
|
-
state.editing = id || null
|
|
1898
|
-
$("editor-title").textContent = id ? "Edit record" : "New record"
|
|
1899
|
-
$("editor-id").value = id || ""
|
|
1900
|
-
$("editor-id").readOnly = Boolean(id)
|
|
1901
|
-
$("editor-delete").hidden = !id
|
|
1902
|
-
if (id) {
|
|
1903
|
-
const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
1904
|
-
$("editor-value").value = JSON.stringify(record.value, null, 2)
|
|
1905
|
-
} else {
|
|
1906
|
-
$("editor-value").value = "{\\n \\n}"
|
|
1907
|
-
}
|
|
1908
|
-
editor.showModal()
|
|
1909
|
-
}
|
|
1910
|
-
$("new-record").addEventListener("click", () => openEditor(null).catch(showError))
|
|
1911
|
-
$("editor-form").addEventListener("submit", (event) => {
|
|
1912
|
-
const submitter = event.submitter
|
|
1913
|
-
if (!submitter || submitter.value !== "save") return
|
|
1914
|
-
event.preventDefault()
|
|
1915
|
-
let value
|
|
1916
|
-
try { value = JSON.parse($("editor-value").value) }
|
|
1917
|
-
catch { showError(new Error("Value is not JSON")); return }
|
|
1918
|
-
const id = $("editor-id").value.trim()
|
|
1919
|
-
const path = "/state/" + encodeURIComponent(state.collection)
|
|
1920
|
-
const request = state.editing
|
|
1921
|
-
? api("PUT", path + "/" + encodeURIComponent(id), { value })
|
|
1922
|
-
: api("POST", path, { id: id || undefined, value })
|
|
1923
|
-
request.then(() => { editor.close(); return renderState() }).catch(showError)
|
|
1924
|
-
})
|
|
1925
|
-
$("editor-delete").addEventListener("click", () => {
|
|
1926
|
-
const id = state.editing
|
|
1927
|
-
if (!id) return
|
|
1928
|
-
api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
1929
|
-
.then(() => { editor.close(); return renderState() })
|
|
1930
|
-
.catch(showError)
|
|
1931
|
-
})
|
|
1932
|
-
|
|
1933
|
-
document.querySelectorAll("[data-advance]").forEach((button) => {
|
|
1934
|
-
button.addEventListener("click", () => {
|
|
1935
|
-
api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
|
|
1936
|
-
})
|
|
1937
|
-
})
|
|
1938
|
-
$("freeze").addEventListener("click", () => {
|
|
1939
|
-
const frozen = $("freeze").dataset.frozen === "1"
|
|
1940
|
-
api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
|
|
1941
|
-
})
|
|
1942
|
-
$("clock-reset").addEventListener("click", () => {
|
|
1943
|
-
api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
|
|
1944
|
-
})
|
|
1945
|
-
$("clock-set").addEventListener("submit", (event) => {
|
|
1946
|
-
event.preventDefault()
|
|
1947
|
-
const raw = $("clock-instant").value.trim()
|
|
1948
|
-
const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
|
|
1949
|
-
api("POST", "/clock", { set }).then(renderClock).catch(showError)
|
|
1950
|
-
})
|
|
1951
|
-
|
|
1952
|
-
$("fault-form").addEventListener("submit", (event) => {
|
|
1953
|
-
event.preventDefault()
|
|
1954
|
-
const preset = $("fault-preset").value
|
|
1955
|
-
const body = preset
|
|
1956
|
-
? { preset }
|
|
1957
|
-
: {
|
|
1958
|
-
status: Number($("fault-status").value),
|
|
1959
|
-
...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
|
|
1960
|
-
...($("fault-method").value ? { method: $("fault-method").value } : {})
|
|
1961
|
-
}
|
|
1962
|
-
if (!preset && !Number.isFinite(body.status)) {
|
|
1963
|
-
showError(new Error("Status must be a number, or choose a preset"))
|
|
1964
|
-
return
|
|
1965
|
-
}
|
|
1966
|
-
api("POST", "/faults", body).then(renderFaults).catch(showError)
|
|
1967
|
-
})
|
|
1968
|
-
$("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
|
|
1969
|
-
$("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
|
|
1970
|
-
|
|
1971
|
-
$("route-list").addEventListener("click", (event) => {
|
|
1972
|
-
const button = event.target.closest("[data-route]")
|
|
1973
|
-
if (!button) return
|
|
1974
|
-
state.route = button.dataset.route
|
|
1975
|
-
$("route-title").textContent = state.route
|
|
1976
|
-
const method = state.route.split(" ")[0]
|
|
1977
|
-
$("route-body").disabled = method === "GET" || method === "DELETE"
|
|
1978
|
-
})
|
|
1979
|
-
$("route-form").addEventListener("submit", async (event) => {
|
|
1980
|
-
event.preventDefault()
|
|
1981
|
-
if (!state.route) return showError(new Error("Choose a route first"))
|
|
1982
|
-
const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
|
|
1983
|
-
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
|
|
1984
|
-
let body
|
|
1985
|
-
if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
|
|
1986
|
-
try { body = JSON.parse($("route-body").value) }
|
|
1987
|
-
catch { return showError(new Error("Body is not JSON")) }
|
|
1988
|
-
}
|
|
1989
|
-
try {
|
|
1990
|
-
const data = await api(method, concrete, body)
|
|
1991
|
-
$("route-result").textContent = JSON.stringify(data, null, 2)
|
|
1992
|
-
} catch (error) {
|
|
1993
|
-
showError(error)
|
|
1994
|
-
}
|
|
1995
|
-
})
|
|
1996
|
-
|
|
1997
|
-
function mountPanels(panels) {
|
|
1998
|
-
const nav = $("nav")
|
|
1999
|
-
const views = $("views")
|
|
2000
|
-
for (const panel of panels) {
|
|
2001
|
-
const button = document.createElement("button")
|
|
2002
|
-
button.type = "button"
|
|
2003
|
-
button.dataset.view = panel.id
|
|
2004
|
-
button.textContent = panel.title
|
|
2005
|
-
nav.append(button)
|
|
2006
|
-
const section = document.createElement("section")
|
|
2007
|
-
section.className = "view"
|
|
2008
|
-
section.id = "view-" + panel.id
|
|
2009
|
-
section.hidden = true
|
|
2010
|
-
const title = document.createElement("h2")
|
|
2011
|
-
title.textContent = panel.title
|
|
2012
|
-
const lede = document.createElement("p")
|
|
2013
|
-
lede.className = "lede"
|
|
2014
|
-
lede.textContent = panel.description || ""
|
|
2015
|
-
const body = document.createElement("div")
|
|
2016
|
-
body.className = "panel-body"
|
|
2017
|
-
body.id = "panel-" + panel.id
|
|
2018
|
-
body.innerHTML = panel.html || ""
|
|
2019
|
-
section.append(title, lede, body)
|
|
2020
|
-
views.append(section)
|
|
2021
|
-
if (panel.script) {
|
|
2022
|
-
try {
|
|
2023
|
-
const run = new Function("root", "api", panel.script)
|
|
2024
|
-
run(body, panelApi)
|
|
2025
|
-
} catch (error) {
|
|
2026
|
-
const note = document.createElement("p")
|
|
2027
|
-
note.className = "banner show"
|
|
2028
|
-
note.textContent = error instanceof Error ? error.message : String(error)
|
|
2029
|
-
body.append(note)
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
loadNamespaces()
|
|
2036
|
-
.then(() => api("GET", "/ui/manifest"))
|
|
2037
|
-
.then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
|
|
2038
|
-
.catch(showError)
|
|
2039
|
-
})()
|
|
2040
|
-
</script>
|
|
2841
|
+
${script}
|
|
2041
2842
|
</body>
|
|
2042
2843
|
</html>`;
|
|
2043
2844
|
};
|
|
@@ -2054,10 +2855,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
|
|
|
2054
2855
|
});
|
|
2055
2856
|
var adminUiRoutes = (service, ui) => {
|
|
2056
2857
|
const shell = () => renderAdminDocument(service);
|
|
2057
|
-
const
|
|
2858
|
+
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
2058
2859
|
return {
|
|
2059
|
-
"GET /ui": () => htmlResponse(
|
|
2060
|
-
"GET /ui/": () => htmlResponse(
|
|
2860
|
+
"GET /ui": () => htmlResponse(document3()),
|
|
2861
|
+
"GET /ui/": () => htmlResponse(document3()),
|
|
2061
2862
|
"GET /ui/manifest": () => json2({
|
|
2062
2863
|
service,
|
|
2063
2864
|
panels: (ui?.panels ?? []).map((panel) => ({
|
|
@@ -2083,11 +2884,11 @@ var EXTENSION_KEYS = {
|
|
|
2083
2884
|
};
|
|
2084
2885
|
|
|
2085
2886
|
// ../../openapi/metadata/dist/read.js
|
|
2086
|
-
var
|
|
2887
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2087
2888
|
var extensionOf = (holder, key) => holder[key];
|
|
2088
2889
|
var operationMetadata = (operation) => {
|
|
2089
2890
|
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
2090
|
-
const ext =
|
|
2891
|
+
const ext = isRecord3(raw) ? raw : {};
|
|
2091
2892
|
const supported = ext.supported ?? true;
|
|
2092
2893
|
const parity = ext.parity ?? {};
|
|
2093
2894
|
return {
|
|
@@ -2123,9 +2924,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
2123
2924
|
this.name = "OperationRegistryError";
|
|
2124
2925
|
}
|
|
2125
2926
|
};
|
|
2126
|
-
var verifyOperations = (
|
|
2927
|
+
var verifyOperations = (document3, handlers) => {
|
|
2127
2928
|
const problems = [];
|
|
2128
|
-
const operations = listOperations(
|
|
2929
|
+
const operations = listOperations(document3);
|
|
2129
2930
|
const seen = /* @__PURE__ */ new Set();
|
|
2130
2931
|
for (const operation of operations) {
|
|
2131
2932
|
if (seen.has(operation.operationId))
|
|
@@ -2241,7 +3042,7 @@ var SAMPLE = 25;
|
|
|
2241
3042
|
var WALK_LIMIT = 1e4;
|
|
2242
3043
|
var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
|
|
2243
3044
|
var fail2 = (status, message) => ({ ok: false, status, message });
|
|
2244
|
-
var
|
|
3045
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2245
3046
|
var kindOf = (value) => {
|
|
2246
3047
|
if (value === null)
|
|
2247
3048
|
return "null";
|
|
@@ -2340,7 +3141,7 @@ var absorb = (accum, value) => {
|
|
|
2340
3141
|
const kind = kindOf(value);
|
|
2341
3142
|
accum.kinds.add(kind);
|
|
2342
3143
|
accum.present++;
|
|
2343
|
-
if (!
|
|
3144
|
+
if (!isRecord4(value))
|
|
2344
3145
|
return;
|
|
2345
3146
|
for (const [key, child] of Object.entries(value)) {
|
|
2346
3147
|
const nested = accum.nested.get(key) ?? blankAccum(key);
|
|
@@ -2365,7 +3166,7 @@ var inferFields = (records) => {
|
|
|
2365
3166
|
if (records.length === 0)
|
|
2366
3167
|
return [];
|
|
2367
3168
|
const sample = records.slice(0, SAMPLE);
|
|
2368
|
-
const objects = sample.filter((record) =>
|
|
3169
|
+
const objects = sample.filter((record) => isRecord4(record.value));
|
|
2369
3170
|
if (objects.length === 0) {
|
|
2370
3171
|
const kinds = new Set(sample.map((record) => kindOf(record.value)));
|
|
2371
3172
|
const list = [...kinds];
|
|
@@ -2552,7 +3353,7 @@ var patchStateRecord = (scope, name, id, value) => {
|
|
|
2552
3353
|
const parsed = jsonValue(value);
|
|
2553
3354
|
if (!parsed.ok)
|
|
2554
3355
|
return parsed;
|
|
2555
|
-
const next =
|
|
3356
|
+
const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
|
|
2556
3357
|
return replaceStateRecord(scope, name, id, next);
|
|
2557
3358
|
};
|
|
2558
3359
|
var deleteStateRecord = (scope, name, id) => {
|
|
@@ -2620,7 +3421,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2620
3421
|
const scope = open(namespace);
|
|
2621
3422
|
if (!scope.ok)
|
|
2622
3423
|
return fromResult(scope, 201);
|
|
2623
|
-
if (!
|
|
3424
|
+
if (!isRecord4(body) || !("value" in body)) {
|
|
2624
3425
|
return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
|
|
2625
3426
|
}
|
|
2626
3427
|
const id = body.id;
|
|
@@ -2638,7 +3439,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2638
3439
|
const scope = open(namespace);
|
|
2639
3440
|
if (!scope.ok)
|
|
2640
3441
|
return fromResult(scope, 200);
|
|
2641
|
-
if (!
|
|
3442
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2642
3443
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2643
3444
|
const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2644
3445
|
if (replaced.ok)
|
|
@@ -2649,7 +3450,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2649
3450
|
const scope = open(namespace);
|
|
2650
3451
|
if (!scope.ok)
|
|
2651
3452
|
return fromResult(scope, 200);
|
|
2652
|
-
if (!
|
|
3453
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2653
3454
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2654
3455
|
const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2655
3456
|
if (patched.ok)
|
|
@@ -2669,14 +3470,14 @@ var stateAdminRoutes = (routes) => {
|
|
|
2669
3470
|
};
|
|
2670
3471
|
|
|
2671
3472
|
// ../core/dist/version.js
|
|
2672
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3473
|
+
var PACKAGE_VERSION = true ? "1.5.1" : UNRELEASED_VERSION;
|
|
2673
3474
|
|
|
2674
3475
|
// ../core/dist/webhooks.js
|
|
2675
3476
|
var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2676
3477
|
var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
|
|
2677
|
-
var
|
|
3478
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2678
3479
|
var parseEndpoint = (value) => {
|
|
2679
|
-
if (!
|
|
3480
|
+
if (!isRecord5(value) || typeof value.url !== "string")
|
|
2680
3481
|
return "each endpoint needs a url";
|
|
2681
3482
|
try {
|
|
2682
3483
|
new URL(value.url);
|
|
@@ -2693,12 +3494,12 @@ var parseEndpoint = (value) => {
|
|
|
2693
3494
|
const events = value.events ?? value.enabledEvents;
|
|
2694
3495
|
if (Array.isArray(events))
|
|
2695
3496
|
endpoint.events = events.map(String);
|
|
2696
|
-
if (
|
|
3497
|
+
if (isRecord5(value.tags)) {
|
|
2697
3498
|
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
2698
3499
|
}
|
|
2699
3500
|
if (typeof value.account === "string")
|
|
2700
3501
|
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
2701
|
-
if (
|
|
3502
|
+
if (isRecord5(value.headers)) {
|
|
2702
3503
|
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
2703
3504
|
}
|
|
2704
3505
|
return endpoint;
|
|
@@ -2725,7 +3526,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
2725
3526
|
return json4(200, { status: "ok" });
|
|
2726
3527
|
},
|
|
2727
3528
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
2728
|
-
if (!
|
|
3529
|
+
if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
2729
3530
|
return adminError3(400, "mode must be duplicate, reorder or drop");
|
|
2730
3531
|
}
|
|
2731
3532
|
const fault = { mode: body.mode };
|
|
@@ -2741,7 +3542,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
2741
3542
|
}))
|
|
2742
3543
|
}),
|
|
2743
3544
|
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
2744
|
-
const list = Array.isArray(body) ? body :
|
|
3545
|
+
const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
|
|
2745
3546
|
if (!Array.isArray(list))
|
|
2746
3547
|
return adminError3(400, "expected [{url, secret?, events?}]");
|
|
2747
3548
|
const parsed = [];
|
|
@@ -2811,8 +3612,8 @@ var DroppedConnectionError = class extends TypeError {
|
|
|
2811
3612
|
this.name = "TypeError";
|
|
2812
3613
|
}
|
|
2813
3614
|
};
|
|
2814
|
-
var operationMatcher = (
|
|
2815
|
-
const matchers = listOperations(
|
|
3615
|
+
var operationMatcher = (document3) => {
|
|
3616
|
+
const matchers = listOperations(document3).map((operation) => ({
|
|
2816
3617
|
operationId: operation.operationId,
|
|
2817
3618
|
method: operation.method.toUpperCase(),
|
|
2818
3619
|
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
@@ -2907,10 +3708,10 @@ var createRuntime = (options) => {
|
|
|
2907
3708
|
const ensureBranch = (namespace, branch2, at) => {
|
|
2908
3709
|
if (!BRANCH_PATTERN2.test(branch2))
|
|
2909
3710
|
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
2910
|
-
const
|
|
3711
|
+
const history2 = timeline(namespace);
|
|
2911
3712
|
if (branch2 === "main") {
|
|
2912
3713
|
if (at !== void 0) {
|
|
2913
|
-
const point =
|
|
3714
|
+
const point = history2.checkout("main", at);
|
|
2914
3715
|
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
2915
3716
|
captured.set(namespace, point.value.snapshot);
|
|
2916
3717
|
rng.setState(point.value.rngState);
|
|
@@ -2923,10 +3724,10 @@ var createRuntime = (options) => {
|
|
|
2923
3724
|
return namespace;
|
|
2924
3725
|
}
|
|
2925
3726
|
const storage = physicalBranch(namespace, branch2);
|
|
2926
|
-
if (!
|
|
3727
|
+
if (!history2.hasBranch(branch2)) {
|
|
2927
3728
|
if (at === void 0)
|
|
2928
|
-
|
|
2929
|
-
const point =
|
|
3729
|
+
history2.commit(capture(namespace));
|
|
3730
|
+
const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
|
|
2930
3731
|
const branchRng = createRng(options.seed ?? 0);
|
|
2931
3732
|
if (point)
|
|
2932
3733
|
branchRng.setState(point.value.rngState);
|
|
@@ -2935,8 +3736,8 @@ var createRuntime = (options) => {
|
|
|
2935
3736
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
2936
3737
|
if (point)
|
|
2937
3738
|
captured.set(storage, point.value.snapshot);
|
|
2938
|
-
} else if (at !== void 0 &&
|
|
2939
|
-
const point =
|
|
3739
|
+
} else if (at !== void 0 && history2.head(branch2)?.id !== at) {
|
|
3740
|
+
const point = history2.checkout(branch2, at);
|
|
2940
3741
|
if (!instances.has(storage)) {
|
|
2941
3742
|
const branchRng = createRng(options.seed ?? 0);
|
|
2942
3743
|
branchRng.setState(point.value.rngState);
|
|
@@ -2947,7 +3748,7 @@ var createRuntime = (options) => {
|
|
|
2947
3748
|
captured.set(storage, point.value.snapshot);
|
|
2948
3749
|
} else {
|
|
2949
3750
|
if (!instances.has(storage)) {
|
|
2950
|
-
const point =
|
|
3751
|
+
const point = history2.head(branch2);
|
|
2951
3752
|
const branchRng = createRng(options.seed ?? 0);
|
|
2952
3753
|
if (point)
|
|
2953
3754
|
branchRng.setState(point.value.rngState);
|
|
@@ -2971,8 +3772,8 @@ var createRuntime = (options) => {
|
|
|
2971
3772
|
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
2972
3773
|
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
2973
3774
|
const branchName = checkoutOptions.branch ?? "main";
|
|
2974
|
-
const
|
|
2975
|
-
const point =
|
|
3775
|
+
const history2 = timeline(namespace);
|
|
3776
|
+
const point = history2.checkout(branchName, checkpointId);
|
|
2976
3777
|
const storage = ensureBranch(namespace, branchName);
|
|
2977
3778
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
2978
3779
|
captured.set(storage, point.value.snapshot);
|
|
@@ -3022,9 +3823,9 @@ var createRuntime = (options) => {
|
|
|
3022
3823
|
instance(name);
|
|
3023
3824
|
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
3024
3825
|
captured.set(name, from);
|
|
3025
|
-
const
|
|
3026
|
-
if (
|
|
3027
|
-
|
|
3826
|
+
const history2 = timelines.get(name);
|
|
3827
|
+
if (history2)
|
|
3828
|
+
history2.commit(capture(name), { branch: "main" });
|
|
3028
3829
|
else
|
|
3029
3830
|
timeline(name);
|
|
3030
3831
|
};
|
|
@@ -3253,10 +4054,10 @@ var createRuntime = (options) => {
|
|
|
3253
4054
|
},
|
|
3254
4055
|
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
3255
4056
|
inspect: (name) => {
|
|
3256
|
-
const
|
|
4057
|
+
const history2 = timeline(name);
|
|
3257
4058
|
return {
|
|
3258
|
-
branches:
|
|
3259
|
-
checkpoints:
|
|
4059
|
+
branches: history2.branches(),
|
|
4060
|
+
checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
3260
4061
|
id,
|
|
3261
4062
|
branch: branchName,
|
|
3262
4063
|
parent,
|
|
@@ -3271,11 +4072,11 @@ var createRuntime = (options) => {
|
|
|
3271
4072
|
} : {},
|
|
3272
4073
|
routes: {
|
|
3273
4074
|
...credentialRoutes(credentials),
|
|
3274
|
-
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3275
4075
|
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
3276
4076
|
...options.admin?.(runtime) ?? {},
|
|
3277
|
-
//
|
|
3278
|
-
//
|
|
4077
|
+
// Shell routes win a colliding key. Fault presets are one of them: the admin page
|
|
4078
|
+
// maps `presets`, and a service route must not replace that list with a record.
|
|
4079
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3279
4080
|
...stateAdminRoutes({
|
|
3280
4081
|
open: stateScope,
|
|
3281
4082
|
afterWrite: (namespace) => {
|
|
@@ -3350,9 +4151,12 @@ var credentialRoutes = (registry) => ({
|
|
|
3350
4151
|
}
|
|
3351
4152
|
});
|
|
3352
4153
|
var presetRoutes = (presets, runtime) => ({
|
|
3353
|
-
"GET /faults/presets": () =>
|
|
3354
|
-
|
|
3355
|
-
|
|
4154
|
+
"GET /faults/presets": () => {
|
|
4155
|
+
const body = {
|
|
4156
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
4157
|
+
};
|
|
4158
|
+
return adminJson(200, body);
|
|
4159
|
+
},
|
|
3356
4160
|
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
3357
4161
|
const name = params.name;
|
|
3358
4162
|
if (!presets[name])
|
|
@@ -3363,7 +4167,7 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
3363
4167
|
});
|
|
3364
4168
|
|
|
3365
4169
|
// src/generated/openapi.ts
|
|
3366
|
-
var
|
|
4170
|
+
var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Payload CMS REST API (Mockingbird subset)","description":"Stateful mock subset of Payload CMS's auto-generated collection REST API: find documents\\n(with a \`where\` query subset, \`limit\`, \`page\`, \`sort\`) and find one by id. Hand-authored\\nfrom Payload's REST API docs and the consumer's response type (payload-cms-response.type.ts).\\n","version":"2","x-mockingbird-upstream":{"note":"Shapes from https://payloadcms.com/docs/rest-api/overview and https://payloadcms.com/docs/queries/overview; the consumer is apps/backend/src/modules/cms/payload-cms/payload-cms.service.ts."}},"servers":[{"url":"https://payload.acme.example"}],"paths":{"/api/{collection}":{"parameters":[{"name":"collection","in":"path","required":true,"schema":{"type":"string","enum":["marketing"]}}],"get":{"operationId":"FindDocuments","description":"Paginated find. \`where[<field>][<operator>]=<value>\` supports equals, not_equals, in, not_in, exists, greater_than(_equal), less_than(_equal), like, contains, and nested \`where[and|or][<i>]\u2026\`. Only the consumer's filters are declared here for parity walks.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"where[type][equals]","in":"query","required":false,"schema":{"type":"string","enum":["referral","banner","email"]}},{"name":"where[isActive][equals]","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":100}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":20}},{"name":"sort","in":"query","required":false,"schema":{"type":"string","enum":["createdAt","-createdAt","name","-name","id","-id"]}}],"responses":{"200":{"description":"A page of documents","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedDocs"}}}},"400":{"description":"Invalid query","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}},"404":{"description":"Unknown collection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}}}}},"/api/{collection}/{id}":{"parameters":[{"name":"collection","in":"path","required":true,"schema":{"type":"string","enum":["marketing"]}},{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"The seeded documents are 1-3; anything else is a 404.","pattern":"^[1-5]$"}}],"get":{"operationId":"FindDocumentById","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The document","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketingDoc"}}}},"404":{"description":"Unknown document or collection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Errors"}}}}}}}},"components":{"schemas":{"MarketingDoc":{"type":"object","required":["id","name","type","createdAt","updatedAt"],"properties":{"id":{"type":"integer","description":"Integer ids, as Payload's Postgres adapter issues them (the consumer types \`id\` as a number)."},"name":{"type":"string"},"type":{"type":"string","enum":["referral","banner","email"]},"isActive":{"type":"boolean"},"cardTitle":{"type":["string","null"]},"cardSubtitle":{"type":["string","null"]},"cardDescription":{"type":["string","null"]},"ctaTitle":{"type":["string","null"]},"ctaActionText":{"type":["string","null"]},"shareMessage":{"type":["string","null"]},"createdAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updatedAt":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"PaginatedDocs":{"type":"object","required":["docs","totalDocs","limit","totalPages","page","pagingCounter","hasPrevPage","hasNextPage","prevPage","nextPage"],"properties":{"docs":{"type":"array","items":{"$ref":"#/components/schemas/MarketingDoc"}},"totalDocs":{"type":"integer"},"limit":{"type":"integer"},"totalPages":{"type":"integer"},"page":{"type":"integer"},"pagingCounter":{"type":"integer"},"hasPrevPage":{"type":"boolean"},"hasNextPage":{"type":"boolean"},"prevPage":{"type":["integer","null"]},"nextPage":{"type":["integer","null"]}}},"Errors":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","items":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}}}}`);
|
|
3367
4171
|
var operationIds = ["FindDocuments", "FindDocumentById"];
|
|
3368
4172
|
var supportedOperationIds = ["FindDocuments", "FindDocumentById"];
|
|
3369
4173
|
|
|
@@ -3524,9 +4328,9 @@ var PAYLOAD_CMS_PRESETS = {
|
|
|
3524
4328
|
};
|
|
3525
4329
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3526
4330
|
var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
3527
|
-
var
|
|
4331
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3528
4332
|
var toDocs = (value, now) => {
|
|
3529
|
-
if (!Array.isArray(value) || !value.every(
|
|
4333
|
+
if (!Array.isArray(value) || !value.every(isRecord6)) return "expected {docs: [{\u2026}]}";
|
|
3530
4334
|
return value.map((doc, index) => ({
|
|
3531
4335
|
createdAt: now,
|
|
3532
4336
|
updatedAt: now,
|
|
@@ -3542,18 +4346,18 @@ var adminRoutes = (runtime) => ({
|
|
|
3542
4346
|
},
|
|
3543
4347
|
"PUT /collections/:slug": ({ params, body, namespace }) => {
|
|
3544
4348
|
const docs = toDocs(
|
|
3545
|
-
|
|
4349
|
+
isRecord6(body) ? body.docs : void 0,
|
|
3546
4350
|
new Date(runtime.clock.now()).toISOString()
|
|
3547
4351
|
);
|
|
3548
4352
|
if (typeof docs === "string") return adminError4(400, docs);
|
|
3549
4353
|
return json5(200, runtime.instance(namespace).state.replace(params.slug, docs));
|
|
3550
4354
|
},
|
|
3551
4355
|
"POST /collections/:slug/docs": ({ params, body, namespace }) => {
|
|
3552
|
-
if (!
|
|
4356
|
+
if (!isRecord6(body)) return adminError4(400, "expected a document object");
|
|
3553
4357
|
return json5(201, runtime.instance(namespace).addDoc(params.slug, body));
|
|
3554
4358
|
},
|
|
3555
4359
|
"PATCH /collections/:slug/docs/:id": ({ params, body, namespace }) => {
|
|
3556
|
-
if (!
|
|
4360
|
+
if (!isRecord6(body)) return adminError4(400, "expected a partial document object");
|
|
3557
4361
|
const doc = runtime.instance(namespace).updateDoc(params.slug, Number(params.id), body);
|
|
3558
4362
|
return doc ? json5(200, doc) : adminError4(404, `no document ${params.slug}/${params.id}`);
|
|
3559
4363
|
},
|
|
@@ -3561,7 +4365,7 @@ var adminRoutes = (runtime) => ({
|
|
|
3561
4365
|
});
|
|
3562
4366
|
var createRuntime2 = (options = {}) => createRuntime({
|
|
3563
4367
|
name: PAYLOAD_CMS_NAMESPACE,
|
|
3564
|
-
document,
|
|
4368
|
+
document: document2,
|
|
3565
4369
|
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
3566
4370
|
...options.clock ? { clock: options.clock } : {},
|
|
3567
4371
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
@@ -3600,8 +4404,8 @@ var OPERATORS = [
|
|
|
3600
4404
|
"like",
|
|
3601
4405
|
"contains"
|
|
3602
4406
|
];
|
|
3603
|
-
var
|
|
3604
|
-
var fieldValue = (doc, path) => path.split(".").reduce((value, key) =>
|
|
4407
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4408
|
+
var fieldValue = (doc, path) => path.split(".").reduce((value, key) => isRecord7(value) ? value[key] : void 0, doc);
|
|
3605
4409
|
var cast = (raw, sample) => {
|
|
3606
4410
|
if (typeof raw !== "string") return raw;
|
|
3607
4411
|
if (typeof sample === "boolean") return raw === "true" ? true : raw === "false" ? false : raw;
|
|
@@ -3647,11 +4451,11 @@ var test = (operator, actual, raw) => {
|
|
|
3647
4451
|
};
|
|
3648
4452
|
var compileWhere = (where, fields) => {
|
|
3649
4453
|
if (where === void 0) return { ok: true, match: () => true };
|
|
3650
|
-
if (!
|
|
4454
|
+
if (!isRecord7(where)) return { ok: false, message: "The where query is malformed." };
|
|
3651
4455
|
const parts = [];
|
|
3652
4456
|
for (const [key, condition] of Object.entries(where)) {
|
|
3653
4457
|
if (key === "and" || key === "or") {
|
|
3654
|
-
const nested = (Array.isArray(condition) ? condition :
|
|
4458
|
+
const nested = (Array.isArray(condition) ? condition : isRecord7(condition) ? Object.values(condition) : []).map((item) => compileWhere(item, fields));
|
|
3655
4459
|
const failed = nested.find((c) => !c.ok);
|
|
3656
4460
|
if (failed) return failed;
|
|
3657
4461
|
const matchers = nested.map((c) => c.match);
|
|
@@ -3664,7 +4468,7 @@ var compileWhere = (where, fields) => {
|
|
|
3664
4468
|
if (!fields.has(root)) {
|
|
3665
4469
|
return { ok: false, message: `The following path cannot be queried: ${key}` };
|
|
3666
4470
|
}
|
|
3667
|
-
if (!
|
|
4471
|
+
if (!isRecord7(condition))
|
|
3668
4472
|
return { ok: false, message: `The following path cannot be queried: ${key}` };
|
|
3669
4473
|
for (const [operator, value] of Object.entries(condition)) {
|
|
3670
4474
|
if (!OPERATORS.includes(operator)) {
|
|
@@ -3711,7 +4515,7 @@ var PayloadCmsAPI = class {
|
|
|
3711
4515
|
}
|
|
3712
4516
|
});
|
|
3713
4517
|
this.service = createService({
|
|
3714
|
-
document,
|
|
4518
|
+
document: document2,
|
|
3715
4519
|
handlers,
|
|
3716
4520
|
sqlite,
|
|
3717
4521
|
namespace,
|
|
@@ -3820,7 +4624,7 @@ var PayloadCmsAPI = class {
|
|
|
3820
4624
|
};
|
|
3821
4625
|
|
|
3822
4626
|
export {
|
|
3823
|
-
document,
|
|
4627
|
+
document2 as document,
|
|
3824
4628
|
operationIds,
|
|
3825
4629
|
supportedOperationIds,
|
|
3826
4630
|
MARKETING_FIELDS,
|
|
@@ -3833,4 +4637,4 @@ export {
|
|
|
3833
4637
|
compileWhere,
|
|
3834
4638
|
PayloadCmsAPI
|
|
3835
4639
|
};
|
|
3836
|
-
//# sourceMappingURL=chunk-
|
|
4640
|
+
//# sourceMappingURL=chunk-XOIML4HJ.js.map
|