@crvouga/mockingbird-service-slack 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/{chunk-OHW2JUR3.js → chunk-PGHLICIK.js} +2 -2
- package/dist/{chunk-N46A45RB.js → chunk-XLLGBYTB.js} +1418 -614
- package/dist/chunk-XLLGBYTB.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-N46A45RB.js.map +0 -7
- /package/dist/{chunk-OHW2JUR3.js.map → chunk-PGHLICIK.js.map} +0 -0
|
@@ -540,10 +540,10 @@ var OpenAPIReferenceError = class extends Error {
|
|
|
540
540
|
};
|
|
541
541
|
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
542
542
|
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
543
|
-
var resolveRef = (
|
|
543
|
+
var resolveRef = (document3, ref) => {
|
|
544
544
|
if (!ref.startsWith("#/"))
|
|
545
545
|
throw new OpenAPIReferenceError(ref);
|
|
546
|
-
let cursor =
|
|
546
|
+
let cursor = document3;
|
|
547
547
|
for (const raw of ref.slice(2).split("/")) {
|
|
548
548
|
const segment = unescapePointer(raw);
|
|
549
549
|
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
@@ -555,14 +555,14 @@ var resolveRef = (document2, ref) => {
|
|
|
555
555
|
throw new OpenAPIReferenceError(ref);
|
|
556
556
|
return cursor;
|
|
557
557
|
};
|
|
558
|
-
var deref = (
|
|
558
|
+
var deref = (document3, value) => {
|
|
559
559
|
let current = value;
|
|
560
560
|
const seen = /* @__PURE__ */ new Set();
|
|
561
561
|
while (isReference(current)) {
|
|
562
562
|
if (seen.has(current.$ref))
|
|
563
563
|
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
564
564
|
seen.add(current.$ref);
|
|
565
|
-
current = resolveRef(
|
|
565
|
+
current = resolveRef(document3, current.$ref);
|
|
566
566
|
}
|
|
567
567
|
return current;
|
|
568
568
|
};
|
|
@@ -580,36 +580,36 @@ var HTTP_METHODS = [
|
|
|
580
580
|
];
|
|
581
581
|
|
|
582
582
|
// ../../openapi/core/dist/document.js
|
|
583
|
-
var mergeParameters = (
|
|
583
|
+
var mergeParameters = (document3, item, own) => {
|
|
584
584
|
const merged = /* @__PURE__ */ new Map();
|
|
585
585
|
for (const raw of item.parameters ?? []) {
|
|
586
|
-
const parameter = deref(
|
|
586
|
+
const parameter = deref(document3, raw);
|
|
587
587
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
588
588
|
}
|
|
589
589
|
for (const raw of own ?? []) {
|
|
590
|
-
const parameter = deref(
|
|
590
|
+
const parameter = deref(document3, raw);
|
|
591
591
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
592
592
|
}
|
|
593
593
|
return [...merged.values()];
|
|
594
594
|
};
|
|
595
|
-
var listOperations = (
|
|
595
|
+
var listOperations = (document3) => {
|
|
596
596
|
const operations = [];
|
|
597
|
-
for (const [path, item] of Object.entries(
|
|
597
|
+
for (const [path, item] of Object.entries(document3.paths)) {
|
|
598
598
|
for (const method of HTTP_METHODS) {
|
|
599
599
|
const operation = item[method];
|
|
600
600
|
if (operation?.operationId === void 0)
|
|
601
601
|
continue;
|
|
602
602
|
const responses = {};
|
|
603
603
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
604
|
-
responses[status] = deref(
|
|
604
|
+
responses[status] = deref(document3, response);
|
|
605
605
|
}
|
|
606
606
|
operations.push({
|
|
607
607
|
operationId: operation.operationId,
|
|
608
608
|
method,
|
|
609
609
|
path,
|
|
610
610
|
operation,
|
|
611
|
-
parameters: mergeParameters(
|
|
612
|
-
requestBody: operation.requestBody === void 0 ? void 0 : deref(
|
|
611
|
+
parameters: mergeParameters(document3, item, operation.parameters),
|
|
612
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
|
|
613
613
|
responses
|
|
614
614
|
});
|
|
615
615
|
}
|
|
@@ -1193,6 +1193,1232 @@ var clearNamespace = (sqlite, namespace) => {
|
|
|
1193
1193
|
});
|
|
1194
1194
|
};
|
|
1195
1195
|
|
|
1196
|
+
// ../../ui/dist/reset.js
|
|
1197
|
+
var CSS_RESET = `*,
|
|
1198
|
+
*::before,
|
|
1199
|
+
*::after {
|
|
1200
|
+
box-sizing: border-box;
|
|
1201
|
+
margin: 0;
|
|
1202
|
+
padding: 0;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
* {
|
|
1206
|
+
scrollbar-width: thin;
|
|
1207
|
+
scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
*::-webkit-scrollbar {
|
|
1211
|
+
width: 12px;
|
|
1212
|
+
height: 12px;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
*::-webkit-scrollbar-track,
|
|
1216
|
+
*::-webkit-scrollbar-corner {
|
|
1217
|
+
background: Canvas;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
*::-webkit-scrollbar-thumb {
|
|
1221
|
+
background: color-mix(in srgb, CanvasText 35%, Canvas);
|
|
1222
|
+
border: 3px solid Canvas;
|
|
1223
|
+
border-radius: 999px;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
html {
|
|
1227
|
+
hanging-punctuation: first allow-end last;
|
|
1228
|
+
-webkit-text-size-adjust: none;
|
|
1229
|
+
text-size-adjust: none;
|
|
1230
|
+
tab-size: 4;
|
|
1231
|
+
font-family:
|
|
1232
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
|
|
1233
|
+
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1234
|
+
font-size: 16px;
|
|
1235
|
+
line-height: 1.5;
|
|
1236
|
+
-webkit-font-smoothing: antialiased;
|
|
1237
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1238
|
+
text-rendering: optimizeLegibility;
|
|
1239
|
+
-webkit-tap-highlight-color: transparent;
|
|
1240
|
+
scroll-behavior: smooth;
|
|
1241
|
+
background: Canvas;
|
|
1242
|
+
color: CanvasText;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
body {
|
|
1246
|
+
min-height: 100svh;
|
|
1247
|
+
line-height: 1.5;
|
|
1248
|
+
background: Canvas;
|
|
1249
|
+
color: CanvasText;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
h1 {
|
|
1253
|
+
font-size: 2em;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
h2 {
|
|
1257
|
+
font-size: 1.5em;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
h3 {
|
|
1261
|
+
font-size: 1.17em;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
h4 {
|
|
1265
|
+
font-size: 1em;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
h5 {
|
|
1269
|
+
font-size: 0.83em;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
h6 {
|
|
1273
|
+
font-size: 0.67em;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
h1,
|
|
1277
|
+
h2,
|
|
1278
|
+
h3,
|
|
1279
|
+
h4,
|
|
1280
|
+
h5,
|
|
1281
|
+
h6 {
|
|
1282
|
+
font-weight: 700;
|
|
1283
|
+
line-height: 1.2;
|
|
1284
|
+
text-wrap: balance;
|
|
1285
|
+
overflow-wrap: break-word;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
p,
|
|
1289
|
+
li,
|
|
1290
|
+
figcaption,
|
|
1291
|
+
dd,
|
|
1292
|
+
dt,
|
|
1293
|
+
blockquote {
|
|
1294
|
+
overflow-wrap: break-word;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
p,
|
|
1298
|
+
li,
|
|
1299
|
+
figcaption,
|
|
1300
|
+
blockquote {
|
|
1301
|
+
text-wrap: pretty;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
ul,
|
|
1305
|
+
ol,
|
|
1306
|
+
menu {
|
|
1307
|
+
list-style: none;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
a {
|
|
1311
|
+
color: inherit;
|
|
1312
|
+
text-decoration: underline;
|
|
1313
|
+
text-decoration-thickness: 1px;
|
|
1314
|
+
text-underline-offset: 0.15em;
|
|
1315
|
+
text-decoration-skip-ink: auto;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
b,
|
|
1319
|
+
strong {
|
|
1320
|
+
font-weight: 700;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
small {
|
|
1324
|
+
font-size: 0.875em;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
code,
|
|
1328
|
+
kbd,
|
|
1329
|
+
samp,
|
|
1330
|
+
pre {
|
|
1331
|
+
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1332
|
+
font-size: 1em;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
sub,
|
|
1336
|
+
sup {
|
|
1337
|
+
font-size: 75%;
|
|
1338
|
+
line-height: 0;
|
|
1339
|
+
position: relative;
|
|
1340
|
+
vertical-align: baseline;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
sub {
|
|
1344
|
+
bottom: -0.25em;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
sup {
|
|
1348
|
+
top: -0.5em;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
abbr[title] {
|
|
1352
|
+
text-decoration: underline dotted;
|
|
1353
|
+
text-underline-offset: 0.15em;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
mark {
|
|
1357
|
+
background: Mark;
|
|
1358
|
+
color: MarkText;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
hr {
|
|
1362
|
+
height: 0;
|
|
1363
|
+
border: 0;
|
|
1364
|
+
border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1365
|
+
color: inherit;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
img,
|
|
1369
|
+
picture,
|
|
1370
|
+
video,
|
|
1371
|
+
canvas {
|
|
1372
|
+
display: block;
|
|
1373
|
+
max-width: 100%;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
img,
|
|
1377
|
+
video {
|
|
1378
|
+
height: auto;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
svg {
|
|
1382
|
+
overflow: hidden;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
iframe {
|
|
1386
|
+
border: 0;
|
|
1387
|
+
max-width: 100%;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
table {
|
|
1391
|
+
border-collapse: collapse;
|
|
1392
|
+
border-spacing: 0;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
fieldset {
|
|
1396
|
+
border: 0;
|
|
1397
|
+
min-inline-size: 0;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
legend {
|
|
1401
|
+
padding: 0;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
button,
|
|
1405
|
+
input,
|
|
1406
|
+
select,
|
|
1407
|
+
textarea,
|
|
1408
|
+
optgroup {
|
|
1409
|
+
font: inherit;
|
|
1410
|
+
letter-spacing: inherit;
|
|
1411
|
+
color: inherit;
|
|
1412
|
+
margin: 0;
|
|
1413
|
+
vertical-align: middle;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
button,
|
|
1417
|
+
input[type="button"],
|
|
1418
|
+
input[type="submit"],
|
|
1419
|
+
input[type="reset"] {
|
|
1420
|
+
appearance: none;
|
|
1421
|
+
background: none;
|
|
1422
|
+
border: 0;
|
|
1423
|
+
padding: 0;
|
|
1424
|
+
cursor: pointer;
|
|
1425
|
+
text-align: center;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
input,
|
|
1429
|
+
textarea,
|
|
1430
|
+
select {
|
|
1431
|
+
background-color: Canvas;
|
|
1432
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1433
|
+
border-radius: 0;
|
|
1434
|
+
padding: 0.5em 0.65em;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
input[type="checkbox"],
|
|
1438
|
+
input[type="radio"],
|
|
1439
|
+
input[type="range"],
|
|
1440
|
+
input[type="file"],
|
|
1441
|
+
input[type="color"],
|
|
1442
|
+
input[type="button"],
|
|
1443
|
+
input[type="submit"],
|
|
1444
|
+
input[type="reset"],
|
|
1445
|
+
input[type="image"],
|
|
1446
|
+
input[type="hidden"] {
|
|
1447
|
+
background: none;
|
|
1448
|
+
border: 0;
|
|
1449
|
+
padding: 0;
|
|
1450
|
+
border-radius: 0;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
input[type="checkbox"],
|
|
1454
|
+
input[type="radio"] {
|
|
1455
|
+
accent-color: CanvasText;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
input[type="search"] {
|
|
1459
|
+
appearance: none;
|
|
1460
|
+
background-color: Canvas;
|
|
1461
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
input[type="search"]::-webkit-search-decoration,
|
|
1465
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
1466
|
+
input[type="search"]::-webkit-search-results-button,
|
|
1467
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
1468
|
+
-webkit-appearance: none;
|
|
1469
|
+
display: none;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
input[type="number"] {
|
|
1473
|
+
appearance: textfield;
|
|
1474
|
+
background-color: Canvas;
|
|
1475
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
1479
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
1480
|
+
-webkit-appearance: none;
|
|
1481
|
+
margin: 0;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
textarea {
|
|
1485
|
+
resize: vertical;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
::placeholder {
|
|
1489
|
+
opacity: 1;
|
|
1490
|
+
color: color-mix(in srgb, CanvasText 45%, transparent);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
:focus-visible {
|
|
1494
|
+
outline: 2px solid CanvasText;
|
|
1495
|
+
outline-offset: 2px;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
:focus:not(:focus-visible) {
|
|
1499
|
+
outline: none;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
:disabled,
|
|
1503
|
+
[disabled] {
|
|
1504
|
+
cursor: default;
|
|
1505
|
+
opacity: 0.6;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
label,
|
|
1509
|
+
summary,
|
|
1510
|
+
select,
|
|
1511
|
+
[role="button"] {
|
|
1512
|
+
cursor: pointer;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
summary {
|
|
1516
|
+
display: list-item;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
details {
|
|
1520
|
+
display: block;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
dialog {
|
|
1524
|
+
margin: auto;
|
|
1525
|
+
padding: 1rem;
|
|
1526
|
+
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1527
|
+
background: Canvas;
|
|
1528
|
+
color: CanvasText;
|
|
1529
|
+
max-width: calc(100vw - 2rem);
|
|
1530
|
+
max-height: calc(100svh - 2rem);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
dialog:not([open]) {
|
|
1534
|
+
display: none;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
::backdrop {
|
|
1538
|
+
background: color-mix(in srgb, CanvasText 40%, transparent);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
[hidden] {
|
|
1542
|
+
display: none !important;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
::selection {
|
|
1546
|
+
background: Highlight;
|
|
1547
|
+
color: HighlightText;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1551
|
+
*,
|
|
1552
|
+
*::before,
|
|
1553
|
+
*::after {
|
|
1554
|
+
animation-duration: 0.01ms !important;
|
|
1555
|
+
animation-iteration-count: 1 !important;
|
|
1556
|
+
transition-duration: 0.01ms !important;
|
|
1557
|
+
scroll-behavior: auto !important;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
@media (pointer: coarse) {
|
|
1562
|
+
input,
|
|
1563
|
+
select,
|
|
1564
|
+
textarea {
|
|
1565
|
+
font-size: max(16px, 1em);
|
|
1566
|
+
}
|
|
1567
|
+
}`;
|
|
1568
|
+
|
|
1569
|
+
// ../core/dist/admin-client.js
|
|
1570
|
+
function isRecord2(value) {
|
|
1571
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1572
|
+
}
|
|
1573
|
+
function mountAdminBrand(doc, host, service, catalog) {
|
|
1574
|
+
const clip = (value, max) => {
|
|
1575
|
+
if (typeof value !== "string")
|
|
1576
|
+
return null;
|
|
1577
|
+
const trimmed = value.trim();
|
|
1578
|
+
if (trimmed === "")
|
|
1579
|
+
return null;
|
|
1580
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
1581
|
+
};
|
|
1582
|
+
const httpUrl = (value) => {
|
|
1583
|
+
if (typeof value !== "string" || value.length > 2e3)
|
|
1584
|
+
return null;
|
|
1585
|
+
try {
|
|
1586
|
+
const url = new URL(value);
|
|
1587
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1588
|
+
return null;
|
|
1589
|
+
return url.href;
|
|
1590
|
+
} catch {
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
const hexColor = (value) => {
|
|
1595
|
+
if (typeof value !== "string")
|
|
1596
|
+
return null;
|
|
1597
|
+
const raw = value.trim();
|
|
1598
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
|
|
1599
|
+
};
|
|
1600
|
+
const hostname = (value) => {
|
|
1601
|
+
try {
|
|
1602
|
+
return new URL(value).hostname.replace(/^www\./, "");
|
|
1603
|
+
} catch {
|
|
1604
|
+
return value;
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
host.replaceChildren();
|
|
1608
|
+
host.hidden = true;
|
|
1609
|
+
if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
|
|
1610
|
+
return;
|
|
1611
|
+
const row = catalog[service];
|
|
1612
|
+
if (!isRecord2(row))
|
|
1613
|
+
return;
|
|
1614
|
+
const vendor = clip(row.vendor, 80);
|
|
1615
|
+
const website = httpUrl(row.website);
|
|
1616
|
+
const guide = httpUrl(row.guide);
|
|
1617
|
+
const docs = httpUrl(row.docs);
|
|
1618
|
+
const logo = httpUrl(row.logo);
|
|
1619
|
+
const color = hexColor(row.color);
|
|
1620
|
+
const description = clip(row.description, 280);
|
|
1621
|
+
if (vendor === null || website === null && guide === null && docs === null)
|
|
1622
|
+
return;
|
|
1623
|
+
const add = (node) => {
|
|
1624
|
+
host.append(node);
|
|
1625
|
+
return node;
|
|
1626
|
+
};
|
|
1627
|
+
if (logo !== null) {
|
|
1628
|
+
const img = doc.createElement("img");
|
|
1629
|
+
img.alt = "";
|
|
1630
|
+
img.src = logo;
|
|
1631
|
+
img.width = 16;
|
|
1632
|
+
img.height = 16;
|
|
1633
|
+
img.decoding = "async";
|
|
1634
|
+
img.referrerPolicy = "no-referrer";
|
|
1635
|
+
if (color !== null)
|
|
1636
|
+
img.style.borderColor = color;
|
|
1637
|
+
add(img);
|
|
1638
|
+
}
|
|
1639
|
+
const name = doc.createElement("span");
|
|
1640
|
+
name.className = "vendor-name";
|
|
1641
|
+
name.textContent = vendor;
|
|
1642
|
+
if (description !== null)
|
|
1643
|
+
name.title = description;
|
|
1644
|
+
add(name);
|
|
1645
|
+
const sep = () => {
|
|
1646
|
+
const node = doc.createElement("span");
|
|
1647
|
+
node.className = "vendor-sep";
|
|
1648
|
+
node.setAttribute("aria-hidden", "true");
|
|
1649
|
+
node.textContent = "\xB7";
|
|
1650
|
+
return node;
|
|
1651
|
+
};
|
|
1652
|
+
const link = (href, label, title) => {
|
|
1653
|
+
const node = doc.createElement("a");
|
|
1654
|
+
node.className = "vendor-link";
|
|
1655
|
+
node.href = href;
|
|
1656
|
+
node.target = "_blank";
|
|
1657
|
+
node.rel = "noopener noreferrer";
|
|
1658
|
+
node.referrerPolicy = "no-referrer";
|
|
1659
|
+
node.textContent = label;
|
|
1660
|
+
node.title = title;
|
|
1661
|
+
return node;
|
|
1662
|
+
};
|
|
1663
|
+
if (website !== null) {
|
|
1664
|
+
add(sep());
|
|
1665
|
+
add(link(website, hostname(website), `${vendor} website`));
|
|
1666
|
+
}
|
|
1667
|
+
if (guide !== null) {
|
|
1668
|
+
add(sep());
|
|
1669
|
+
add(link(guide, "Docs", `Docs for ${vendor}`));
|
|
1670
|
+
}
|
|
1671
|
+
if (docs !== null) {
|
|
1672
|
+
add(sep());
|
|
1673
|
+
add(link(docs, "API", `${vendor} API reference`));
|
|
1674
|
+
}
|
|
1675
|
+
host.removeAttribute("hidden");
|
|
1676
|
+
host.hidden = false;
|
|
1677
|
+
}
|
|
1678
|
+
function startAdminBrand(env) {
|
|
1679
|
+
const requested = env.params.get("brands");
|
|
1680
|
+
let brandsUrl = env.brandsUrl;
|
|
1681
|
+
if (requested) {
|
|
1682
|
+
try {
|
|
1683
|
+
const url = new URL(requested, env.location.href);
|
|
1684
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
|
|
1685
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && local)
|
|
1686
|
+
brandsUrl = url.href;
|
|
1687
|
+
} catch {
|
|
1688
|
+
brandsUrl = env.brandsUrl;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
const vendorHost = env.$("vendor");
|
|
1692
|
+
const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
|
|
1693
|
+
const brandService = brandRoot?.getAttribute("data-service") || "";
|
|
1694
|
+
if (vendorHost === null)
|
|
1695
|
+
return;
|
|
1696
|
+
env.fetch(brandsUrl, {
|
|
1697
|
+
headers: { accept: "application/json" },
|
|
1698
|
+
signal: AbortSignal.timeout(5e3)
|
|
1699
|
+
}).then(async (response) => {
|
|
1700
|
+
if (!response.ok)
|
|
1701
|
+
return null;
|
|
1702
|
+
const catalog = await response.json();
|
|
1703
|
+
return catalog;
|
|
1704
|
+
}).then((catalog) => {
|
|
1705
|
+
mountAdminBrand(env.document, vendorHost, brandService, catalog);
|
|
1706
|
+
}).catch(() => {
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
function faultPresetList(value) {
|
|
1710
|
+
if (!isRecord2(value))
|
|
1711
|
+
throw new Error("Fault presets response was not an object");
|
|
1712
|
+
if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
|
|
1713
|
+
return { presets: [] };
|
|
1714
|
+
}
|
|
1715
|
+
if (!Array.isArray(value.presets))
|
|
1716
|
+
throw new Error("Fault presets response was not a list");
|
|
1717
|
+
return {
|
|
1718
|
+
presets: value.presets.flatMap((item) => {
|
|
1719
|
+
if (!isRecord2(item) || typeof item.name !== "string")
|
|
1720
|
+
return [];
|
|
1721
|
+
return [{ name: item.name }];
|
|
1722
|
+
})
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
function bootAdmin(config) {
|
|
1726
|
+
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1727
|
+
const sources = ["declared", "inferred", "mixed"];
|
|
1728
|
+
const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
|
|
1729
|
+
const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
|
|
1730
|
+
const readJson2 = (text2) => {
|
|
1731
|
+
const value = JSON.parse(text2);
|
|
1732
|
+
return value;
|
|
1733
|
+
};
|
|
1734
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
|
|
1735
|
+
switch (char) {
|
|
1736
|
+
case "&":
|
|
1737
|
+
return "&";
|
|
1738
|
+
case "<":
|
|
1739
|
+
return "<";
|
|
1740
|
+
case ">":
|
|
1741
|
+
return ">";
|
|
1742
|
+
case '"':
|
|
1743
|
+
return """;
|
|
1744
|
+
default:
|
|
1745
|
+
return "'";
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
const el = (id, kind) => {
|
|
1749
|
+
const node = document.getElementById(id);
|
|
1750
|
+
if (!(node instanceof kind))
|
|
1751
|
+
throw new Error(`admin shell is missing #${id}`);
|
|
1752
|
+
return node;
|
|
1753
|
+
};
|
|
1754
|
+
const errorMessage = (data, fallback) => {
|
|
1755
|
+
if (!isRecord2(data))
|
|
1756
|
+
return fallback;
|
|
1757
|
+
const error = data.error;
|
|
1758
|
+
if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
|
|
1759
|
+
return fallback;
|
|
1760
|
+
}
|
|
1761
|
+
return error.message;
|
|
1762
|
+
};
|
|
1763
|
+
const requireList = (value, label) => {
|
|
1764
|
+
if (value === void 0 || value === null)
|
|
1765
|
+
return [];
|
|
1766
|
+
if (!Array.isArray(value))
|
|
1767
|
+
throw new Error(`${label} was not a list`);
|
|
1768
|
+
return value;
|
|
1769
|
+
};
|
|
1770
|
+
const state = {
|
|
1771
|
+
namespace: "default",
|
|
1772
|
+
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1773
|
+
view: "overview",
|
|
1774
|
+
collection: "",
|
|
1775
|
+
shape: null,
|
|
1776
|
+
route: null,
|
|
1777
|
+
editing: null
|
|
1778
|
+
};
|
|
1779
|
+
const standard = new Set(config.standardRoutes);
|
|
1780
|
+
const banner = el("banner", HTMLElement);
|
|
1781
|
+
const keyInput = el("admin-key", HTMLInputElement);
|
|
1782
|
+
keyInput.value = state.key;
|
|
1783
|
+
const params = new URLSearchParams(location.search);
|
|
1784
|
+
const queryKey = params.get("key");
|
|
1785
|
+
if (queryKey) {
|
|
1786
|
+
state.key = queryKey;
|
|
1787
|
+
keyInput.value = state.key;
|
|
1788
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
1789
|
+
params.delete("key");
|
|
1790
|
+
const next = params.toString();
|
|
1791
|
+
history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
|
|
1792
|
+
}
|
|
1793
|
+
const queryNamespace = params.get("namespace");
|
|
1794
|
+
if (queryNamespace)
|
|
1795
|
+
state.namespace = queryNamespace;
|
|
1796
|
+
const showError = (error) => {
|
|
1797
|
+
banner.textContent = error instanceof Error ? error.message : String(error);
|
|
1798
|
+
banner.classList.add("show");
|
|
1799
|
+
};
|
|
1800
|
+
const clearError = () => {
|
|
1801
|
+
banner.classList.remove("show");
|
|
1802
|
+
};
|
|
1803
|
+
const api = async (method, path, body) => {
|
|
1804
|
+
const headers = { accept: "application/json" };
|
|
1805
|
+
if (state.key !== "")
|
|
1806
|
+
headers[config.adminKeyHeader] = state.key;
|
|
1807
|
+
if (state.namespace !== "")
|
|
1808
|
+
headers["x-mockingbird-namespace"] = state.namespace;
|
|
1809
|
+
if (body !== void 0)
|
|
1810
|
+
headers["content-type"] = "application/json";
|
|
1811
|
+
const init = { method, headers };
|
|
1812
|
+
if (body !== void 0)
|
|
1813
|
+
init.body = JSON.stringify(body);
|
|
1814
|
+
const response = await fetch(config.adminPrefix + path, init);
|
|
1815
|
+
const text2 = await response.text();
|
|
1816
|
+
let data = null;
|
|
1817
|
+
if (text2 !== "") {
|
|
1818
|
+
try {
|
|
1819
|
+
data = readJson2(text2);
|
|
1820
|
+
} catch {
|
|
1821
|
+
data = { raw: text2 };
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
if (response.status === 401) {
|
|
1825
|
+
keyInput.focus();
|
|
1826
|
+
throw new Error(errorMessage(data, "Admin key required"));
|
|
1827
|
+
}
|
|
1828
|
+
if (!response.ok) {
|
|
1829
|
+
throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
|
|
1830
|
+
}
|
|
1831
|
+
return data;
|
|
1832
|
+
};
|
|
1833
|
+
const originGet = async (path) => {
|
|
1834
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
1835
|
+
if (!response.ok)
|
|
1836
|
+
throw new Error(`${response.status} ${path}`);
|
|
1837
|
+
const value = await response.json();
|
|
1838
|
+
return value;
|
|
1839
|
+
};
|
|
1840
|
+
const panelApi = {
|
|
1841
|
+
get: (path) => api("GET", path),
|
|
1842
|
+
send: (method, path, body) => api(method, path, body),
|
|
1843
|
+
namespace: () => state.namespace
|
|
1844
|
+
};
|
|
1845
|
+
const isPanelScript = (value) => typeof value === "function";
|
|
1846
|
+
const setTheme = (theme) => {
|
|
1847
|
+
if (theme)
|
|
1848
|
+
document.documentElement.dataset.theme = theme;
|
|
1849
|
+
else
|
|
1850
|
+
delete document.documentElement.dataset.theme;
|
|
1851
|
+
};
|
|
1852
|
+
const storedTheme = localStorage.getItem("mockingbird-admin-theme");
|
|
1853
|
+
if (storedTheme)
|
|
1854
|
+
setTheme(storedTheme);
|
|
1855
|
+
el("theme", HTMLButtonElement).addEventListener("click", () => {
|
|
1856
|
+
const current = document.documentElement.dataset.theme;
|
|
1857
|
+
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
1858
|
+
const next = dark ? "light" : "dark";
|
|
1859
|
+
localStorage.setItem("mockingbird-admin-theme", next);
|
|
1860
|
+
setTheme(next);
|
|
1861
|
+
});
|
|
1862
|
+
const show = (view) => {
|
|
1863
|
+
state.view = view;
|
|
1864
|
+
for (const button of document.querySelectorAll("#nav button")) {
|
|
1865
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1866
|
+
continue;
|
|
1867
|
+
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
|
|
1868
|
+
}
|
|
1869
|
+
for (const section of document.querySelectorAll("main .view")) {
|
|
1870
|
+
if (!(section instanceof HTMLElement))
|
|
1871
|
+
continue;
|
|
1872
|
+
section.hidden = section.id !== `view-${view}`;
|
|
1873
|
+
}
|
|
1874
|
+
};
|
|
1875
|
+
el("nav", HTMLElement).addEventListener("click", (event) => {
|
|
1876
|
+
const target = event.target;
|
|
1877
|
+
if (!(target instanceof Element))
|
|
1878
|
+
return;
|
|
1879
|
+
const button = target.closest("button[data-view]");
|
|
1880
|
+
if (!(button instanceof HTMLButtonElement))
|
|
1881
|
+
return;
|
|
1882
|
+
const view = button.dataset.view;
|
|
1883
|
+
if (view === void 0)
|
|
1884
|
+
return;
|
|
1885
|
+
show(view);
|
|
1886
|
+
refresh().catch(showError);
|
|
1887
|
+
});
|
|
1888
|
+
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>`;
|
|
1889
|
+
const table = (columns, rows) => {
|
|
1890
|
+
if (rows.length === 0)
|
|
1891
|
+
return '<p class="empty">Nothing here yet.</p>';
|
|
1892
|
+
const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
|
|
1893
|
+
const body = rows.map((row) => {
|
|
1894
|
+
const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
|
|
1895
|
+
const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
|
|
1896
|
+
return `<tr${attrs}>${cells}</tr>`;
|
|
1897
|
+
}).join("");
|
|
1898
|
+
return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
|
1899
|
+
};
|
|
1900
|
+
const preview = (value) => {
|
|
1901
|
+
const text2 = JSON.stringify(value) ?? "null";
|
|
1902
|
+
return esc(text2.length > 180 ? `${text2.slice(0, 180)}\u2026` : text2);
|
|
1903
|
+
};
|
|
1904
|
+
const when = (epoch) => {
|
|
1905
|
+
const date = new Date(epoch);
|
|
1906
|
+
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
|
|
1907
|
+
};
|
|
1908
|
+
const clockCard = (epoch) => {
|
|
1909
|
+
const date = new Date(epoch);
|
|
1910
|
+
if (Number.isNaN(date.getTime()))
|
|
1911
|
+
return esc(String(epoch));
|
|
1912
|
+
const iso = date.toISOString();
|
|
1913
|
+
return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
|
|
1914
|
+
};
|
|
1915
|
+
const decodeClock = (value) => {
|
|
1916
|
+
if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
|
|
1917
|
+
throw new Error("Clock response was not a clock");
|
|
1918
|
+
}
|
|
1919
|
+
return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
|
|
1920
|
+
};
|
|
1921
|
+
const decodeField = (value) => {
|
|
1922
|
+
if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
|
|
1923
|
+
return void 0;
|
|
1924
|
+
}
|
|
1925
|
+
const field = {
|
|
1926
|
+
name: value.name,
|
|
1927
|
+
kind: value.kind,
|
|
1928
|
+
optional: value.optional === true
|
|
1929
|
+
};
|
|
1930
|
+
if (typeof value.description === "string")
|
|
1931
|
+
field.description = value.description;
|
|
1932
|
+
if (Array.isArray(value.fields)) {
|
|
1933
|
+
field.fields = value.fields.flatMap((item) => {
|
|
1934
|
+
const child = decodeField(item);
|
|
1935
|
+
return child === void 0 ? [] : [child];
|
|
1936
|
+
});
|
|
1937
|
+
}
|
|
1938
|
+
return field;
|
|
1939
|
+
};
|
|
1940
|
+
const decodeCollection = (value) => {
|
|
1941
|
+
if (!isRecord2(value) || typeof value.name !== "string")
|
|
1942
|
+
return void 0;
|
|
1943
|
+
const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
|
|
1944
|
+
const field = decodeField(item);
|
|
1945
|
+
return field === void 0 ? [] : [field];
|
|
1946
|
+
}) : [];
|
|
1947
|
+
const collection = {
|
|
1948
|
+
name: value.name,
|
|
1949
|
+
label: typeof value.label === "string" ? value.label : value.name,
|
|
1950
|
+
count: typeof value.count === "number" ? value.count : 0,
|
|
1951
|
+
source: isSource(value.source) ? value.source : "inferred",
|
|
1952
|
+
fields
|
|
1953
|
+
};
|
|
1954
|
+
if (typeof value.description === "string")
|
|
1955
|
+
collection.description = value.description;
|
|
1956
|
+
return collection;
|
|
1957
|
+
};
|
|
1958
|
+
const decodeState = (value) => {
|
|
1959
|
+
if (!isRecord2(value) || !Array.isArray(value.collections)) {
|
|
1960
|
+
throw new Error("State response has no collection list");
|
|
1961
|
+
}
|
|
1962
|
+
return {
|
|
1963
|
+
namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
|
|
1964
|
+
storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
|
|
1965
|
+
collections: value.collections.flatMap((item) => {
|
|
1966
|
+
const collection = decodeCollection(item);
|
|
1967
|
+
return collection === void 0 ? [] : [collection];
|
|
1968
|
+
})
|
|
1969
|
+
};
|
|
1970
|
+
};
|
|
1971
|
+
const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
|
|
1972
|
+
if (!isRecord2(item) || typeof item.id !== "string")
|
|
1973
|
+
return [];
|
|
1974
|
+
return [
|
|
1975
|
+
{
|
|
1976
|
+
id: item.id,
|
|
1977
|
+
seq: typeof item.seq === "number" ? item.seq : 0,
|
|
1978
|
+
value: item.value,
|
|
1979
|
+
attrs: ` class="clickable" data-id="${esc(item.id)}"`
|
|
1980
|
+
}
|
|
1981
|
+
];
|
|
1982
|
+
});
|
|
1983
|
+
const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
|
|
1984
|
+
const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
|
|
1985
|
+
if (!isRecord2(item))
|
|
1986
|
+
return [];
|
|
1987
|
+
return [
|
|
1988
|
+
{
|
|
1989
|
+
at: textOf(item.at),
|
|
1990
|
+
method: textOf(item.method),
|
|
1991
|
+
path: textOf(item.path),
|
|
1992
|
+
status: textOf(item.status),
|
|
1993
|
+
durationMs: textOf(item.durationMs),
|
|
1994
|
+
operationId: textOf(item.operationId)
|
|
1995
|
+
}
|
|
1996
|
+
];
|
|
1997
|
+
});
|
|
1998
|
+
const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
|
|
1999
|
+
if (!isRecord2(item))
|
|
2000
|
+
return [];
|
|
2001
|
+
return [
|
|
2002
|
+
{
|
|
2003
|
+
id: textOf(item.id),
|
|
2004
|
+
method: typeof item.method === "string" ? item.method : void 0,
|
|
2005
|
+
pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
|
|
2006
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0,
|
|
2007
|
+
status: typeof item.status === "number" ? item.status : void 0,
|
|
2008
|
+
effect: typeof item.effect === "string" ? item.effect : void 0,
|
|
2009
|
+
drop: typeof item.drop === "boolean" ? item.drop : void 0,
|
|
2010
|
+
latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
|
|
2011
|
+
}
|
|
2012
|
+
];
|
|
2013
|
+
});
|
|
2014
|
+
const loadNamespaces = async () => {
|
|
2015
|
+
const data = await api("GET", "/namespaces");
|
|
2016
|
+
const select = el("namespace", HTMLSelectElement);
|
|
2017
|
+
const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
|
|
2018
|
+
const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
|
|
2019
|
+
const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
|
|
2020
|
+
if (!names.includes(state.namespace))
|
|
2021
|
+
names.unshift(state.namespace);
|
|
2022
|
+
select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
|
|
2023
|
+
select.value = state.namespace;
|
|
2024
|
+
};
|
|
2025
|
+
const renderOverview = async () => {
|
|
2026
|
+
const [health, shape, clock, journal] = await Promise.all([
|
|
2027
|
+
originGet("/health"),
|
|
2028
|
+
api("GET", "/state"),
|
|
2029
|
+
api("GET", "/clock"),
|
|
2030
|
+
api("GET", "/requests?limit=8")
|
|
2031
|
+
]);
|
|
2032
|
+
if (!isRecord2(health))
|
|
2033
|
+
throw new Error("Health response was not an object");
|
|
2034
|
+
const decoded = decodeState(shape);
|
|
2035
|
+
state.shape = decoded;
|
|
2036
|
+
const time = decodeClock(clock);
|
|
2037
|
+
el("overview-cards", HTMLElement).innerHTML = cards([
|
|
2038
|
+
{
|
|
2039
|
+
k: "Service",
|
|
2040
|
+
v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
|
|
2041
|
+
},
|
|
2042
|
+
{
|
|
2043
|
+
k: "Status",
|
|
2044
|
+
v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
|
|
2045
|
+
},
|
|
2046
|
+
{ k: "Namespace", v: esc(state.namespace) },
|
|
2047
|
+
{ k: "Collections", v: String(decoded.collections.length) },
|
|
2048
|
+
{
|
|
2049
|
+
k: "Records",
|
|
2050
|
+
v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
|
|
2051
|
+
},
|
|
2052
|
+
{ k: "Clock", v: clockCard(time.now) }
|
|
2053
|
+
]);
|
|
2054
|
+
el("overview-requests", HTMLElement).innerHTML = table([
|
|
2055
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2056
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2057
|
+
{
|
|
2058
|
+
label: "Status",
|
|
2059
|
+
cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
|
|
2060
|
+
},
|
|
2061
|
+
{ label: "Operation", cell: (row) => esc(row.operationId) }
|
|
2062
|
+
], decodeJournal(journal));
|
|
2063
|
+
};
|
|
2064
|
+
const schemaChips = (collection) => {
|
|
2065
|
+
if (collection.fields.length === 0) {
|
|
2066
|
+
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
|
|
2067
|
+
}
|
|
2068
|
+
return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
|
|
2069
|
+
};
|
|
2070
|
+
const renderState = async () => {
|
|
2071
|
+
const shape = decodeState(await api("GET", "/state"));
|
|
2072
|
+
state.shape = shape;
|
|
2073
|
+
const host = el("collections", HTMLElement);
|
|
2074
|
+
if (shape.collections.length === 0) {
|
|
2075
|
+
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
|
|
2076
|
+
el("schema", HTMLElement).innerHTML = "";
|
|
2077
|
+
el("records", HTMLElement).innerHTML = "";
|
|
2078
|
+
return;
|
|
2079
|
+
}
|
|
2080
|
+
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2081
|
+
const first = shape.collections[0];
|
|
2082
|
+
if (first === void 0)
|
|
2083
|
+
return;
|
|
2084
|
+
state.collection = first.name;
|
|
2085
|
+
}
|
|
2086
|
+
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("");
|
|
2087
|
+
const selected = shape.collections.find((item) => item.name === state.collection);
|
|
2088
|
+
if (selected === void 0)
|
|
2089
|
+
return;
|
|
2090
|
+
const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
|
|
2091
|
+
el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
|
|
2092
|
+
const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
|
|
2093
|
+
el("records", HTMLElement).innerHTML = table([
|
|
2094
|
+
{ label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
|
|
2095
|
+
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2096
|
+
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2097
|
+
], decodeRecords(page));
|
|
2098
|
+
};
|
|
2099
|
+
const renderClock = async () => {
|
|
2100
|
+
const clock = decodeClock(await api("GET", "/clock"));
|
|
2101
|
+
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>`;
|
|
2102
|
+
const freeze = el("freeze", HTMLButtonElement);
|
|
2103
|
+
freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
|
|
2104
|
+
freeze.dataset.frozen = clock.frozen ? "1" : "0";
|
|
2105
|
+
};
|
|
2106
|
+
const renderFaults = async () => {
|
|
2107
|
+
const [faults, presets] = await Promise.all([
|
|
2108
|
+
api("GET", "/faults"),
|
|
2109
|
+
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2110
|
+
]);
|
|
2111
|
+
const select = el("fault-preset", HTMLSelectElement);
|
|
2112
|
+
const current = select.value;
|
|
2113
|
+
const list2 = faultPresetList(presets).presets;
|
|
2114
|
+
select.innerHTML = `<option value="">Custom rule</option>${list2.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
|
|
2115
|
+
select.value = current;
|
|
2116
|
+
el("fault-list", HTMLElement).innerHTML = table([
|
|
2117
|
+
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2118
|
+
{
|
|
2119
|
+
label: "Match",
|
|
2120
|
+
cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
label: "Effect",
|
|
2124
|
+
cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
|
|
2125
|
+
}
|
|
2126
|
+
], decodeFaults(faults));
|
|
2127
|
+
};
|
|
2128
|
+
const renderJournal = async () => {
|
|
2129
|
+
const journal = await api("GET", "/requests?limit=100");
|
|
2130
|
+
el("journal", HTMLElement).innerHTML = table([
|
|
2131
|
+
{ label: "When", cell: (row) => esc(row.at) },
|
|
2132
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2133
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2134
|
+
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2135
|
+
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2136
|
+
], decodeJournal(journal));
|
|
2137
|
+
};
|
|
2138
|
+
const renderRoutes = async () => {
|
|
2139
|
+
const data = await api("GET", "/");
|
|
2140
|
+
if (!isRecord2(data))
|
|
2141
|
+
throw new Error("Route list was not an object");
|
|
2142
|
+
const routes = requireList(data.routes, "Routes").map((route) => String(route));
|
|
2143
|
+
el("route-list", HTMLElement).innerHTML = routes.map((route) => {
|
|
2144
|
+
const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
|
|
2145
|
+
const parts = route.split(" ");
|
|
2146
|
+
const method = parts[0] ?? "";
|
|
2147
|
+
const path = parts.slice(1).join(" ");
|
|
2148
|
+
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>`;
|
|
2149
|
+
}).join("");
|
|
2150
|
+
};
|
|
2151
|
+
const renderers = {
|
|
2152
|
+
overview: renderOverview,
|
|
2153
|
+
state: renderState,
|
|
2154
|
+
clock: renderClock,
|
|
2155
|
+
faults: renderFaults,
|
|
2156
|
+
journal: renderJournal,
|
|
2157
|
+
routes: renderRoutes
|
|
2158
|
+
};
|
|
2159
|
+
async function refresh() {
|
|
2160
|
+
clearError();
|
|
2161
|
+
const render = renderers[state.view];
|
|
2162
|
+
if (render)
|
|
2163
|
+
await render();
|
|
2164
|
+
}
|
|
2165
|
+
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2166
|
+
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2167
|
+
refresh().catch(showError);
|
|
2168
|
+
});
|
|
2169
|
+
keyInput.addEventListener("change", () => {
|
|
2170
|
+
state.key = keyInput.value.trim();
|
|
2171
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2172
|
+
refresh().catch(showError);
|
|
2173
|
+
});
|
|
2174
|
+
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2175
|
+
refresh().catch(showError);
|
|
2176
|
+
});
|
|
2177
|
+
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2178
|
+
const target = event.target;
|
|
2179
|
+
if (!(target instanceof Element))
|
|
2180
|
+
return;
|
|
2181
|
+
const button = target.closest("[data-collection]");
|
|
2182
|
+
if (!(button instanceof HTMLElement))
|
|
2183
|
+
return;
|
|
2184
|
+
const collection = button.dataset.collection;
|
|
2185
|
+
if (collection === void 0)
|
|
2186
|
+
return;
|
|
2187
|
+
state.collection = collection;
|
|
2188
|
+
renderState().catch(showError);
|
|
2189
|
+
});
|
|
2190
|
+
el("records", HTMLElement).addEventListener("click", (event) => {
|
|
2191
|
+
const target = event.target;
|
|
2192
|
+
if (!(target instanceof Element))
|
|
2193
|
+
return;
|
|
2194
|
+
const row = target.closest("[data-id]");
|
|
2195
|
+
if (!(row instanceof HTMLElement))
|
|
2196
|
+
return;
|
|
2197
|
+
const id = row.dataset.id;
|
|
2198
|
+
if (id === void 0)
|
|
2199
|
+
return;
|
|
2200
|
+
openEditor(id).catch(showError);
|
|
2201
|
+
});
|
|
2202
|
+
const editor = el("editor", HTMLDialogElement);
|
|
2203
|
+
const editorId = el("editor-id", HTMLInputElement);
|
|
2204
|
+
const editorValue = el("editor-value", HTMLTextAreaElement);
|
|
2205
|
+
const editorDelete = el("editor-delete", HTMLButtonElement);
|
|
2206
|
+
async function openEditor(id) {
|
|
2207
|
+
state.editing = id;
|
|
2208
|
+
el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
|
|
2209
|
+
editorId.value = id ?? "";
|
|
2210
|
+
editorId.readOnly = id !== null;
|
|
2211
|
+
editorDelete.hidden = id === null;
|
|
2212
|
+
if (id) {
|
|
2213
|
+
const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
|
|
2214
|
+
if (!isRecord2(record))
|
|
2215
|
+
throw new Error("Record response was not an object");
|
|
2216
|
+
editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
|
|
2217
|
+
} else {
|
|
2218
|
+
editorValue.value = "{\n \n}";
|
|
2219
|
+
}
|
|
2220
|
+
editor.showModal();
|
|
2221
|
+
}
|
|
2222
|
+
el("new-record", HTMLButtonElement).addEventListener("click", () => {
|
|
2223
|
+
openEditor(null).catch(showError);
|
|
2224
|
+
});
|
|
2225
|
+
el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2226
|
+
const submitter = event.submitter;
|
|
2227
|
+
if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
|
|
2228
|
+
return;
|
|
2229
|
+
event.preventDefault();
|
|
2230
|
+
let value;
|
|
2231
|
+
try {
|
|
2232
|
+
value = readJson2(editorValue.value);
|
|
2233
|
+
} catch {
|
|
2234
|
+
showError(new Error("Value is not JSON"));
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
const id = editorId.value.trim();
|
|
2238
|
+
const path = `/state/${encodeURIComponent(state.collection)}`;
|
|
2239
|
+
const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
|
|
2240
|
+
request.then(() => {
|
|
2241
|
+
editor.close();
|
|
2242
|
+
return renderState();
|
|
2243
|
+
}).catch(showError);
|
|
2244
|
+
});
|
|
2245
|
+
editorDelete.addEventListener("click", () => {
|
|
2246
|
+
const id = state.editing;
|
|
2247
|
+
if (!id)
|
|
2248
|
+
return;
|
|
2249
|
+
api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
|
|
2250
|
+
editor.close();
|
|
2251
|
+
return renderState();
|
|
2252
|
+
}).catch(showError);
|
|
2253
|
+
});
|
|
2254
|
+
for (const button of document.querySelectorAll("[data-advance]")) {
|
|
2255
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2256
|
+
continue;
|
|
2257
|
+
button.addEventListener("click", () => {
|
|
2258
|
+
const raw = button.dataset.advance;
|
|
2259
|
+
if (raw === void 0)
|
|
2260
|
+
return;
|
|
2261
|
+
api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
el("freeze", HTMLButtonElement).addEventListener("click", () => {
|
|
2265
|
+
const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
|
|
2266
|
+
api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
|
|
2267
|
+
});
|
|
2268
|
+
el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
|
|
2269
|
+
api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
|
|
2270
|
+
});
|
|
2271
|
+
el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2272
|
+
event.preventDefault();
|
|
2273
|
+
const raw = el("clock-instant", HTMLInputElement).value.trim();
|
|
2274
|
+
const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
|
|
2275
|
+
api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
|
|
2276
|
+
});
|
|
2277
|
+
el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2278
|
+
event.preventDefault();
|
|
2279
|
+
const preset = el("fault-preset", HTMLSelectElement).value;
|
|
2280
|
+
if (preset !== "") {
|
|
2281
|
+
api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
const status = Number(el("fault-status", HTMLInputElement).value);
|
|
2285
|
+
if (!Number.isFinite(status)) {
|
|
2286
|
+
showError(new Error("Status must be a number, or choose a preset"));
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
const path = el("fault-path", HTMLInputElement).value;
|
|
2290
|
+
const method = el("fault-method", HTMLInputElement).value;
|
|
2291
|
+
api("POST", "/faults", {
|
|
2292
|
+
status,
|
|
2293
|
+
...path !== "" ? { pathPrefix: path } : {},
|
|
2294
|
+
...method !== "" ? { method } : {}
|
|
2295
|
+
}).then(() => renderFaults()).catch(showError);
|
|
2296
|
+
});
|
|
2297
|
+
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2298
|
+
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2299
|
+
});
|
|
2300
|
+
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2301
|
+
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2302
|
+
});
|
|
2303
|
+
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2304
|
+
const target = event.target;
|
|
2305
|
+
if (!(target instanceof Element))
|
|
2306
|
+
return;
|
|
2307
|
+
const button = target.closest("[data-route]");
|
|
2308
|
+
if (!(button instanceof HTMLElement))
|
|
2309
|
+
return;
|
|
2310
|
+
const route = button.dataset.route;
|
|
2311
|
+
if (route === void 0)
|
|
2312
|
+
return;
|
|
2313
|
+
state.route = route;
|
|
2314
|
+
el("route-title", HTMLElement).textContent = route;
|
|
2315
|
+
const method = route.split(" ")[0] ?? "";
|
|
2316
|
+
el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
|
|
2317
|
+
});
|
|
2318
|
+
el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2319
|
+
event.preventDefault();
|
|
2320
|
+
const send = async () => {
|
|
2321
|
+
if (state.route === null)
|
|
2322
|
+
throw new Error("Choose a route first");
|
|
2323
|
+
const parts = state.route.split(" ");
|
|
2324
|
+
const method = parts[0];
|
|
2325
|
+
const path = parts.slice(1).join(" ");
|
|
2326
|
+
if (method === void 0)
|
|
2327
|
+
throw new Error("Choose a route first");
|
|
2328
|
+
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
|
|
2329
|
+
let body;
|
|
2330
|
+
const routeBody = el("route-body", HTMLTextAreaElement);
|
|
2331
|
+
if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
|
|
2332
|
+
try {
|
|
2333
|
+
body = readJson2(routeBody.value);
|
|
2334
|
+
} catch {
|
|
2335
|
+
throw new Error("Body is not JSON");
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
const data = await api(method, concrete, body);
|
|
2339
|
+
el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
|
|
2340
|
+
};
|
|
2341
|
+
send().catch(showError);
|
|
2342
|
+
});
|
|
2343
|
+
const decodePanel = (value) => {
|
|
2344
|
+
if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
|
|
2345
|
+
return void 0;
|
|
2346
|
+
}
|
|
2347
|
+
const panel = {
|
|
2348
|
+
id: value.id,
|
|
2349
|
+
title: value.title,
|
|
2350
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
2351
|
+
html: typeof value.html === "string" ? value.html : ""
|
|
2352
|
+
};
|
|
2353
|
+
if (typeof value.script === "string")
|
|
2354
|
+
panel.script = value.script;
|
|
2355
|
+
return panel;
|
|
2356
|
+
};
|
|
2357
|
+
const mountPanels = (panels) => {
|
|
2358
|
+
const nav = el("nav", HTMLElement);
|
|
2359
|
+
const views = el("views", HTMLElement);
|
|
2360
|
+
for (const panel of panels) {
|
|
2361
|
+
const button = document.createElement("button");
|
|
2362
|
+
button.type = "button";
|
|
2363
|
+
button.dataset.view = panel.id;
|
|
2364
|
+
button.textContent = panel.title;
|
|
2365
|
+
nav.append(button);
|
|
2366
|
+
const section = document.createElement("section");
|
|
2367
|
+
section.className = "view";
|
|
2368
|
+
section.id = `view-${panel.id}`;
|
|
2369
|
+
section.hidden = true;
|
|
2370
|
+
const title = document.createElement("h2");
|
|
2371
|
+
title.textContent = panel.title;
|
|
2372
|
+
const lede = document.createElement("p");
|
|
2373
|
+
lede.className = "lede";
|
|
2374
|
+
lede.textContent = panel.description;
|
|
2375
|
+
const body = document.createElement("div");
|
|
2376
|
+
body.className = "panel-body";
|
|
2377
|
+
body.id = `panel-${panel.id}`;
|
|
2378
|
+
body.innerHTML = panel.html;
|
|
2379
|
+
section.append(title, lede, body);
|
|
2380
|
+
views.append(section);
|
|
2381
|
+
if (panel.script === void 0)
|
|
2382
|
+
continue;
|
|
2383
|
+
try {
|
|
2384
|
+
const compiled = new Function("root", "api", panel.script);
|
|
2385
|
+
if (!isPanelScript(compiled))
|
|
2386
|
+
throw new Error("panel script did not compile");
|
|
2387
|
+
compiled(body, panelApi);
|
|
2388
|
+
} catch (error) {
|
|
2389
|
+
const note = document.createElement("p");
|
|
2390
|
+
note.className = "banner show";
|
|
2391
|
+
note.textContent = error instanceof Error ? error.message : String(error);
|
|
2392
|
+
body.append(note);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
};
|
|
2396
|
+
startAdminBrand({
|
|
2397
|
+
document,
|
|
2398
|
+
$: (id) => document.getElementById(id),
|
|
2399
|
+
params,
|
|
2400
|
+
location: { href: location.href, origin: location.origin },
|
|
2401
|
+
fetch,
|
|
2402
|
+
brandsUrl: config.brandsUrl
|
|
2403
|
+
});
|
|
2404
|
+
loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
|
|
2405
|
+
const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
|
|
2406
|
+
const decoded = decodePanel(panel);
|
|
2407
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2408
|
+
});
|
|
2409
|
+
mountPanels(panels);
|
|
2410
|
+
return refresh();
|
|
2411
|
+
}).catch(showError);
|
|
2412
|
+
}
|
|
2413
|
+
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2414
|
+
var adminClientSource = (config) => {
|
|
2415
|
+
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2416
|
+
return `<script>
|
|
2417
|
+
${clientPrelude}
|
|
2418
|
+
bootAdmin(${payload});
|
|
2419
|
+
</script>`;
|
|
2420
|
+
};
|
|
2421
|
+
|
|
1196
2422
|
// ../core/dist/surface.js
|
|
1197
2423
|
var defineMock = (runtime) => runtime;
|
|
1198
2424
|
var STANDARD_ADMIN_ROUTES = [
|
|
@@ -1261,94 +2487,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
|
1261
2487
|
return "'";
|
|
1262
2488
|
}
|
|
1263
2489
|
});
|
|
2490
|
+
var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
|
|
1264
2491
|
var renderAdminDocument = (service) => {
|
|
1265
2492
|
const name = escapeHtml(service);
|
|
1266
|
-
const
|
|
2493
|
+
const script = adminClientSource({
|
|
2494
|
+
standardRoutes: STANDARD_ADMIN_ROUTES,
|
|
2495
|
+
adminPrefix: ADMIN_PREFIX,
|
|
2496
|
+
adminKeyHeader: ADMIN_KEY_HEADER,
|
|
2497
|
+
brandsUrl: ADMIN_BRANDS_URL,
|
|
2498
|
+
service
|
|
2499
|
+
});
|
|
1267
2500
|
return `<!DOCTYPE html>
|
|
1268
2501
|
<html lang="en">
|
|
1269
2502
|
<head>
|
|
1270
2503
|
<meta charset="utf-8">
|
|
1271
2504
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1272
2505
|
<meta name="color-scheme" content="light dark">
|
|
1273
|
-
<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='
|
|
2506
|
+
<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">
|
|
1274
2507
|
<title>${name} admin</title>
|
|
1275
2508
|
<style>
|
|
2509
|
+
${CSS_RESET}
|
|
1276
2510
|
:root {
|
|
1277
2511
|
color-scheme: light;
|
|
1278
|
-
--bg: #
|
|
2512
|
+
--bg: #fafafa;
|
|
1279
2513
|
--bg-elev: #ffffff;
|
|
1280
|
-
--bg-muted: #
|
|
1281
|
-
--bg-inset: #
|
|
1282
|
-
--line: #
|
|
1283
|
-
--line-strong: #
|
|
1284
|
-
--ink: #
|
|
1285
|
-
--muted: #
|
|
1286
|
-
--faint: #
|
|
1287
|
-
--accent: #
|
|
1288
|
-
--accent-ink: #
|
|
1289
|
-
--accent-soft: #
|
|
1290
|
-
--ok: #
|
|
1291
|
-
--ok-soft: #
|
|
1292
|
-
--warn: #
|
|
1293
|
-
--warn-soft: #
|
|
1294
|
-
--err: #
|
|
1295
|
-
--err-soft: #
|
|
1296
|
-
--shadow:
|
|
1297
|
-
--sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2514
|
+
--bg-muted: #f4f4f5;
|
|
2515
|
+
--bg-inset: #f4f4f5;
|
|
2516
|
+
--line: #dedee3;
|
|
2517
|
+
--line-strong: #c4c4cc;
|
|
2518
|
+
--ink: #18181b;
|
|
2519
|
+
--muted: #62626b;
|
|
2520
|
+
--faint: #71717a;
|
|
2521
|
+
--accent: #27272a;
|
|
2522
|
+
--accent-ink: #ffffff;
|
|
2523
|
+
--accent-soft: #f4f4f5;
|
|
2524
|
+
--ok: #166534;
|
|
2525
|
+
--ok-soft: #f0fdf4;
|
|
2526
|
+
--warn: #a16207;
|
|
2527
|
+
--warn-soft: #fefce8;
|
|
2528
|
+
--err: #a82d32;
|
|
2529
|
+
--err-soft: #fef2f2;
|
|
2530
|
+
--shadow: none;
|
|
2531
|
+
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1298
2532
|
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1299
|
-
--radius:
|
|
1300
|
-
--header:
|
|
1301
|
-
--nav:
|
|
2533
|
+
--radius: 10px;
|
|
2534
|
+
--header: 56px;
|
|
2535
|
+
--nav: 200px;
|
|
1302
2536
|
}
|
|
1303
2537
|
:root[data-theme="dark"] {
|
|
1304
2538
|
color-scheme: dark;
|
|
1305
|
-
--bg: #
|
|
1306
|
-
--bg-elev: #
|
|
1307
|
-
--bg-muted: #
|
|
1308
|
-
--bg-inset: #
|
|
1309
|
-
--line: #
|
|
1310
|
-
--line-strong: #
|
|
1311
|
-
--ink: #
|
|
1312
|
-
--muted: #
|
|
1313
|
-
--faint: #
|
|
1314
|
-
--accent: #
|
|
1315
|
-
--accent-ink: #
|
|
1316
|
-
--accent-soft: #
|
|
1317
|
-
--ok: #
|
|
1318
|
-
--ok-soft: #
|
|
1319
|
-
--warn: #
|
|
1320
|
-
--warn-soft: #
|
|
1321
|
-
--err: #
|
|
1322
|
-
--err-soft: #
|
|
1323
|
-
--shadow:
|
|
2539
|
+
--bg: #111113;
|
|
2540
|
+
--bg-elev: #19191c;
|
|
2541
|
+
--bg-muted: #242428;
|
|
2542
|
+
--bg-inset: #111113;
|
|
2543
|
+
--line: #36363c;
|
|
2544
|
+
--line-strong: #52525b;
|
|
2545
|
+
--ink: #f4f4f5;
|
|
2546
|
+
--muted: #a9a9b2;
|
|
2547
|
+
--faint: #71717a;
|
|
2548
|
+
--accent: #e4e4e7;
|
|
2549
|
+
--accent-ink: #18181b;
|
|
2550
|
+
--accent-soft: #242428;
|
|
2551
|
+
--ok: #86efac;
|
|
2552
|
+
--ok-soft: #14241b;
|
|
2553
|
+
--warn: #fde68a;
|
|
2554
|
+
--warn-soft: #2a2410;
|
|
2555
|
+
--err: #ffaaaa;
|
|
2556
|
+
--err-soft: #2c1516;
|
|
2557
|
+
--shadow: none;
|
|
1324
2558
|
}
|
|
1325
2559
|
@media (prefers-color-scheme: dark) {
|
|
1326
2560
|
:root:not([data-theme="light"]) {
|
|
1327
2561
|
color-scheme: dark;
|
|
1328
|
-
--bg: #
|
|
1329
|
-
--bg-elev: #
|
|
1330
|
-
--bg-muted: #
|
|
1331
|
-
--bg-inset: #
|
|
1332
|
-
--line: #
|
|
1333
|
-
--line-strong: #
|
|
1334
|
-
--ink: #
|
|
1335
|
-
--muted: #
|
|
1336
|
-
--faint: #
|
|
1337
|
-
--accent: #
|
|
1338
|
-
--accent-ink: #
|
|
1339
|
-
--accent-soft: #
|
|
1340
|
-
--ok: #
|
|
1341
|
-
--ok-soft: #
|
|
1342
|
-
--warn: #
|
|
1343
|
-
--warn-soft: #
|
|
1344
|
-
--err: #
|
|
1345
|
-
--err-soft: #
|
|
1346
|
-
--shadow:
|
|
2562
|
+
--bg: #111113;
|
|
2563
|
+
--bg-elev: #19191c;
|
|
2564
|
+
--bg-muted: #242428;
|
|
2565
|
+
--bg-inset: #111113;
|
|
2566
|
+
--line: #36363c;
|
|
2567
|
+
--line-strong: #52525b;
|
|
2568
|
+
--ink: #f4f4f5;
|
|
2569
|
+
--muted: #a9a9b2;
|
|
2570
|
+
--faint: #71717a;
|
|
2571
|
+
--accent: #e4e4e7;
|
|
2572
|
+
--accent-ink: #18181b;
|
|
2573
|
+
--accent-soft: #242428;
|
|
2574
|
+
--ok: #86efac;
|
|
2575
|
+
--ok-soft: #14241b;
|
|
2576
|
+
--warn: #fde68a;
|
|
2577
|
+
--warn-soft: #2a2410;
|
|
2578
|
+
--err: #ffaaaa;
|
|
2579
|
+
--err-soft: #2c1516;
|
|
2580
|
+
--shadow: none;
|
|
1347
2581
|
}
|
|
1348
2582
|
}
|
|
1349
|
-
|
|
1350
|
-
html
|
|
2583
|
+
/* Fill the viewport, then grow with the content so the background covers the scroll. */
|
|
2584
|
+
html { height: 100%; }
|
|
1351
2585
|
body {
|
|
2586
|
+
margin: 0;
|
|
2587
|
+
min-height: 100%;
|
|
2588
|
+
display: flex;
|
|
2589
|
+
flex-direction: column;
|
|
1352
2590
|
font-family: var(--sans);
|
|
1353
2591
|
background: var(--bg);
|
|
1354
2592
|
color: var(--ink);
|
|
@@ -1357,24 +2595,37 @@ var renderAdminDocument = (service) => {
|
|
|
1357
2595
|
padding-right: env(safe-area-inset-right);
|
|
1358
2596
|
-webkit-text-size-adjust: 100%;
|
|
1359
2597
|
}
|
|
1360
|
-
button, input, select, textarea { font: inherit; color: inherit; }
|
|
1361
|
-
button { cursor: pointer; }
|
|
1362
2598
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1363
|
-
|
|
1364
|
-
* { scroll-behavior: auto !important; transition: none !important; }
|
|
1365
|
-
}
|
|
1366
|
-
.app { min-height: 100%; display: flex; flex-direction: column; }
|
|
2599
|
+
.app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
|
|
1367
2600
|
header.top {
|
|
1368
2601
|
position: sticky; top: 0; z-index: 5;
|
|
1369
2602
|
display: flex; align-items: center; gap: 12px;
|
|
1370
2603
|
min-height: var(--header); padding: 10px 16px;
|
|
1371
|
-
background:
|
|
2604
|
+
background: var(--bg-elev);
|
|
1372
2605
|
border-bottom: 1px solid var(--line);
|
|
1373
|
-
|
|
1374
|
-
}
|
|
1375
|
-
.brand {
|
|
1376
|
-
|
|
1377
|
-
|
|
2606
|
+
min-width: 0; max-width: 100%;
|
|
2607
|
+
}
|
|
2608
|
+
.brand {
|
|
2609
|
+
display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
|
|
2610
|
+
min-width: 0; max-width: 100%; margin-right: auto;
|
|
2611
|
+
}
|
|
2612
|
+
.brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
|
|
2613
|
+
.brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
|
|
2614
|
+
.brand-id span { color: var(--faint); font-size: 12px; }
|
|
2615
|
+
.vendor {
|
|
2616
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
2617
|
+
min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
|
|
2618
|
+
}
|
|
2619
|
+
.vendor[hidden] { display: none; }
|
|
2620
|
+
/* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
|
|
2621
|
+
.vendor img {
|
|
2622
|
+
width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
|
|
2623
|
+
border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
|
|
2624
|
+
}
|
|
2625
|
+
.vendor-name { color: var(--ink); font-weight: 600; }
|
|
2626
|
+
.vendor-sep { color: var(--faint); }
|
|
2627
|
+
.vendor-link { color: inherit; text-decoration: none; }
|
|
2628
|
+
.vendor-link:hover { color: var(--ink); text-decoration: underline; }
|
|
1378
2629
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1379
2630
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
1380
2631
|
label.field input, label.field select, .controls input[type="password"] {
|
|
@@ -1392,7 +2643,7 @@ var renderAdminDocument = (service) => {
|
|
|
1392
2643
|
record or the full nav row would widen the page past the viewport. */
|
|
1393
2644
|
.layout {
|
|
1394
2645
|
display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
|
|
1395
|
-
flex: 1
|
|
2646
|
+
flex: 1 0 auto; min-width: 0;
|
|
1396
2647
|
}
|
|
1397
2648
|
nav.side {
|
|
1398
2649
|
border-right: 1px solid var(--line); padding: 12px;
|
|
@@ -1446,8 +2697,8 @@ var renderAdminDocument = (service) => {
|
|
|
1446
2697
|
.banner.show { display: block; }
|
|
1447
2698
|
.empty { color: var(--muted); padding: 24px 8px; }
|
|
1448
2699
|
dialog {
|
|
1449
|
-
border: 1px solid var(--line); border-radius:
|
|
1450
|
-
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow:
|
|
2700
|
+
border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
|
|
2701
|
+
color: var(--ink); width: min(640px, calc(100vw - 24px)); box-shadow: none;
|
|
1451
2702
|
}
|
|
1452
2703
|
dialog::backdrop { background: rgb(20 19 17 / 0.45); }
|
|
1453
2704
|
dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1474,7 +2725,7 @@ var renderAdminDocument = (service) => {
|
|
|
1474
2725
|
padding-top: max(10px, env(safe-area-inset-top));
|
|
1475
2726
|
}
|
|
1476
2727
|
input, select, textarea { font-size: 16px; }
|
|
1477
|
-
.controls { width: 100%; }
|
|
2728
|
+
.brand, .vendor, .controls { width: 100%; }
|
|
1478
2729
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
1479
2730
|
.layout { grid-template-columns: minmax(0, 1fr); }
|
|
1480
2731
|
nav.side {
|
|
@@ -1505,8 +2756,11 @@ var renderAdminDocument = (service) => {
|
|
|
1505
2756
|
<div class="app" data-service="${name}" data-mockingbird-admin>
|
|
1506
2757
|
<header class="top">
|
|
1507
2758
|
<div class="brand">
|
|
1508
|
-
<
|
|
1509
|
-
|
|
2759
|
+
<div class="brand-id">
|
|
2760
|
+
<strong>${name}</strong>
|
|
2761
|
+
<span>Admin</span>
|
|
2762
|
+
</div>
|
|
2763
|
+
<div class="vendor" id="vendor" hidden></div>
|
|
1510
2764
|
</div>
|
|
1511
2765
|
<div class="controls">
|
|
1512
2766
|
<label class="field">Namespace
|
|
@@ -1636,460 +2890,7 @@ var renderAdminDocument = (service) => {
|
|
|
1636
2890
|
</div>
|
|
1637
2891
|
</form>
|
|
1638
2892
|
</dialog>
|
|
1639
|
-
|
|
1640
|
-
(() => {
|
|
1641
|
-
const STANDARD = new Set(${standard})
|
|
1642
|
-
const state = {
|
|
1643
|
-
namespace: "default",
|
|
1644
|
-
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1645
|
-
view: "overview",
|
|
1646
|
-
collection: "",
|
|
1647
|
-
shape: null,
|
|
1648
|
-
route: null,
|
|
1649
|
-
editing: null
|
|
1650
|
-
}
|
|
1651
|
-
const $ = (id) => document.getElementById(id)
|
|
1652
|
-
const banner = $("banner")
|
|
1653
|
-
const keyInput = $("admin-key")
|
|
1654
|
-
keyInput.value = state.key
|
|
1655
|
-
const params = new URLSearchParams(location.search)
|
|
1656
|
-
if (params.get("key")) {
|
|
1657
|
-
state.key = params.get("key")
|
|
1658
|
-
keyInput.value = state.key
|
|
1659
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1660
|
-
params.delete("key")
|
|
1661
|
-
const next = params.toString()
|
|
1662
|
-
history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
|
|
1663
|
-
}
|
|
1664
|
-
if (params.get("namespace")) state.namespace = params.get("namespace")
|
|
1665
|
-
|
|
1666
|
-
const showError = (error) => {
|
|
1667
|
-
banner.textContent = error instanceof Error ? error.message : String(error)
|
|
1668
|
-
banner.classList.add("show")
|
|
1669
|
-
}
|
|
1670
|
-
const clearError = () => banner.classList.remove("show")
|
|
1671
|
-
|
|
1672
|
-
async function originGet(path) {
|
|
1673
|
-
const response = await fetch(path, { headers: { accept: "application/json" } })
|
|
1674
|
-
if (!response.ok) throw new Error(response.status + " " + path)
|
|
1675
|
-
return response.json()
|
|
1676
|
-
}
|
|
1677
|
-
async function api(method, path, body) {
|
|
1678
|
-
const headers = { accept: "application/json" }
|
|
1679
|
-
if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
|
|
1680
|
-
if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
|
|
1681
|
-
if (body !== undefined) headers["content-type"] = "application/json"
|
|
1682
|
-
const response = await fetch("${ADMIN_PREFIX}" + path, {
|
|
1683
|
-
method,
|
|
1684
|
-
headers,
|
|
1685
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1686
|
-
})
|
|
1687
|
-
const text = await response.text()
|
|
1688
|
-
let data = null
|
|
1689
|
-
if (text) {
|
|
1690
|
-
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
|
1691
|
-
}
|
|
1692
|
-
if (response.status === 401) {
|
|
1693
|
-
keyInput.focus()
|
|
1694
|
-
throw new Error((data && data.error && data.error.message) || "Admin key required")
|
|
1695
|
-
}
|
|
1696
|
-
if (!response.ok) {
|
|
1697
|
-
throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
|
|
1698
|
-
}
|
|
1699
|
-
return data
|
|
1700
|
-
}
|
|
1701
|
-
const panelApi = {
|
|
1702
|
-
get: (path) => api("GET", path),
|
|
1703
|
-
send: (method, path, body) => api(method, path, body),
|
|
1704
|
-
namespace: () => state.namespace
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
function setTheme(theme) {
|
|
1708
|
-
if (theme) document.documentElement.dataset.theme = theme
|
|
1709
|
-
else delete document.documentElement.dataset.theme
|
|
1710
|
-
}
|
|
1711
|
-
const storedTheme = localStorage.getItem("mockingbird-admin-theme")
|
|
1712
|
-
if (storedTheme) setTheme(storedTheme)
|
|
1713
|
-
$("theme").addEventListener("click", () => {
|
|
1714
|
-
const current = document.documentElement.dataset.theme
|
|
1715
|
-
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
|
|
1716
|
-
const next = dark ? "light" : "dark"
|
|
1717
|
-
localStorage.setItem("mockingbird-admin-theme", next)
|
|
1718
|
-
setTheme(next)
|
|
1719
|
-
})
|
|
1720
|
-
|
|
1721
|
-
function show(view) {
|
|
1722
|
-
state.view = view
|
|
1723
|
-
for (const button of document.querySelectorAll("#nav button")) {
|
|
1724
|
-
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
|
|
1725
|
-
}
|
|
1726
|
-
for (const section of document.querySelectorAll("main .view")) {
|
|
1727
|
-
section.hidden = section.id !== "view-" + view
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
document.getElementById("nav").addEventListener("click", (event) => {
|
|
1731
|
-
const button = event.target.closest("button[data-view]")
|
|
1732
|
-
if (!button) return
|
|
1733
|
-
show(button.dataset.view)
|
|
1734
|
-
refresh().catch(showError)
|
|
1735
|
-
})
|
|
1736
|
-
|
|
1737
|
-
function cards(items) {
|
|
1738
|
-
return '<div class="grid">' + items.map((item) =>
|
|
1739
|
-
'<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
|
|
1740
|
-
).join("") + "</div>"
|
|
1741
|
-
}
|
|
1742
|
-
function table(columns, rows) {
|
|
1743
|
-
if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
|
|
1744
|
-
const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
|
|
1745
|
-
const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
|
|
1746
|
-
'<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
|
|
1747
|
-
).join("") + "</tr>").join("")
|
|
1748
|
-
return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
|
|
1749
|
-
}
|
|
1750
|
-
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
1751
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
1752
|
-
}[char]))
|
|
1753
|
-
const preview = (value) => {
|
|
1754
|
-
const text = JSON.stringify(value)
|
|
1755
|
-
return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
|
|
1756
|
-
}
|
|
1757
|
-
const when = (epoch) => {
|
|
1758
|
-
const date = new Date(epoch)
|
|
1759
|
-
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
|
|
1760
|
-
}
|
|
1761
|
-
const clockCard = (epoch) => {
|
|
1762
|
-
const date = new Date(epoch)
|
|
1763
|
-
if (Number.isNaN(date.getTime())) return esc(String(epoch))
|
|
1764
|
-
const iso = date.toISOString()
|
|
1765
|
-
return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
async function loadNamespaces() {
|
|
1769
|
-
const data = await api("GET", "/namespaces")
|
|
1770
|
-
const select = $("namespace")
|
|
1771
|
-
const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
|
|
1772
|
-
if (!names.includes(state.namespace)) names.unshift(state.namespace)
|
|
1773
|
-
select.innerHTML = names.map((name) =>
|
|
1774
|
-
'<option value="' + esc(name) + '">' + esc(name) + "</option>"
|
|
1775
|
-
).join("")
|
|
1776
|
-
select.value = state.namespace
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
async function renderOverview() {
|
|
1780
|
-
const [health, shape, clock, journal] = await Promise.all([
|
|
1781
|
-
originGet("/health"),
|
|
1782
|
-
api("GET", "/state"),
|
|
1783
|
-
api("GET", "/clock"),
|
|
1784
|
-
api("GET", "/requests?limit=8")
|
|
1785
|
-
])
|
|
1786
|
-
state.shape = shape
|
|
1787
|
-
$("overview-cards").innerHTML = cards([
|
|
1788
|
-
{ k: "Service", v: esc(health.service || "${name}") },
|
|
1789
|
-
{ k: "Status", v: esc(health.status || "ok") },
|
|
1790
|
-
{ k: "Namespace", v: esc(state.namespace) },
|
|
1791
|
-
{ k: "Collections", v: String(shape.collections.length) },
|
|
1792
|
-
{ k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
|
|
1793
|
-
{ k: "Clock", v: clockCard(clock.now) }
|
|
1794
|
-
])
|
|
1795
|
-
const requests = (journal && journal.requests) || []
|
|
1796
|
-
$("overview-requests").innerHTML = table(
|
|
1797
|
-
[
|
|
1798
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1799
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1800
|
-
{ label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
|
|
1801
|
-
{ label: "Operation", cell: (row) => esc(row.operationId || "") }
|
|
1802
|
-
],
|
|
1803
|
-
requests
|
|
1804
|
-
)
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
function schemaChips(collection) {
|
|
1808
|
-
if (!collection || collection.fields.length === 0) {
|
|
1809
|
-
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
|
|
1810
|
-
}
|
|
1811
|
-
return '<div class="chips">' + collection.fields.map((field) =>
|
|
1812
|
-
'<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
|
|
1813
|
-
).join("") + "</div>"
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
async function renderState() {
|
|
1817
|
-
const shape = await api("GET", "/state")
|
|
1818
|
-
state.shape = shape
|
|
1819
|
-
const host = $("collections")
|
|
1820
|
-
if (shape.collections.length === 0) {
|
|
1821
|
-
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
|
|
1822
|
-
$("schema").innerHTML = ""
|
|
1823
|
-
$("records").innerHTML = ""
|
|
1824
|
-
return
|
|
1825
|
-
}
|
|
1826
|
-
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
1827
|
-
state.collection = shape.collections[0].name
|
|
1828
|
-
}
|
|
1829
|
-
host.innerHTML = shape.collections.map((item) =>
|
|
1830
|
-
'<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
|
|
1831
|
-
(item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
|
|
1832
|
-
'</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
|
|
1833
|
-
).join("")
|
|
1834
|
-
const selected = shape.collections.find((item) => item.name === state.collection)
|
|
1835
|
-
$("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
|
|
1836
|
-
(selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
|
|
1837
|
-
schemaChips(selected)
|
|
1838
|
-
const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
|
|
1839
|
-
const rows = page.records.map((record) => ({
|
|
1840
|
-
...record,
|
|
1841
|
-
attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
|
|
1842
|
-
}))
|
|
1843
|
-
$("records").innerHTML = table(
|
|
1844
|
-
[
|
|
1845
|
-
{ label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
|
|
1846
|
-
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
1847
|
-
{ label: "Value", cell: (row) => preview(row.value) }
|
|
1848
|
-
],
|
|
1849
|
-
rows
|
|
1850
|
-
)
|
|
1851
|
-
}
|
|
1852
|
-
|
|
1853
|
-
async function renderClock() {
|
|
1854
|
-
const clock = await api("GET", "/clock")
|
|
1855
|
-
$("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
|
|
1856
|
-
"</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
|
|
1857
|
-
(clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
|
|
1858
|
-
$("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
|
|
1859
|
-
$("freeze").dataset.frozen = clock.frozen ? "1" : "0"
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
async function renderFaults() {
|
|
1863
|
-
const [faults, presets] = await Promise.all([
|
|
1864
|
-
api("GET", "/faults"),
|
|
1865
|
-
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
1866
|
-
])
|
|
1867
|
-
const select = $("fault-preset")
|
|
1868
|
-
const current = select.value
|
|
1869
|
-
const list = presets.presets || []
|
|
1870
|
-
select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
|
|
1871
|
-
'<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
|
|
1872
|
-
).join("")
|
|
1873
|
-
select.value = current
|
|
1874
|
-
const rules = faults.faults || []
|
|
1875
|
-
$("fault-list").innerHTML = table(
|
|
1876
|
-
[
|
|
1877
|
-
{ label: "Id", cell: (row) => esc(row.id) },
|
|
1878
|
-
{ label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
|
|
1879
|
-
{ label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
|
|
1880
|
-
],
|
|
1881
|
-
rules
|
|
1882
|
-
)
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
async function renderJournal() {
|
|
1886
|
-
const journal = await api("GET", "/requests?limit=100")
|
|
1887
|
-
$("journal").innerHTML = table(
|
|
1888
|
-
[
|
|
1889
|
-
{ label: "When", cell: (row) => esc(row.at || "") },
|
|
1890
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1891
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1892
|
-
{ label: "Status", cell: (row) => esc(row.status) },
|
|
1893
|
-
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
1894
|
-
],
|
|
1895
|
-
journal.requests || []
|
|
1896
|
-
)
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
async function renderRoutes() {
|
|
1900
|
-
const data = await api("GET", "/")
|
|
1901
|
-
const routes = data.routes || []
|
|
1902
|
-
$("route-list").innerHTML = routes.map((route) => {
|
|
1903
|
-
const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
|
|
1904
|
-
return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
|
|
1905
|
-
'</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
|
|
1906
|
-
'</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
|
|
1907
|
-
}).join("")
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
|
-
const renderers = {
|
|
1911
|
-
overview: renderOverview,
|
|
1912
|
-
state: renderState,
|
|
1913
|
-
clock: renderClock,
|
|
1914
|
-
faults: renderFaults,
|
|
1915
|
-
journal: renderJournal,
|
|
1916
|
-
routes: renderRoutes
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
|
-
async function refresh() {
|
|
1920
|
-
clearError()
|
|
1921
|
-
if (state.view in renderers) await renderers[state.view]()
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
$("namespace").addEventListener("change", () => {
|
|
1925
|
-
state.namespace = $("namespace").value
|
|
1926
|
-
refresh().catch(showError)
|
|
1927
|
-
})
|
|
1928
|
-
keyInput.addEventListener("change", () => {
|
|
1929
|
-
state.key = keyInput.value.trim()
|
|
1930
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1931
|
-
refresh().catch(showError)
|
|
1932
|
-
})
|
|
1933
|
-
$("refresh").addEventListener("click", () => refresh().catch(showError))
|
|
1934
|
-
|
|
1935
|
-
$("collections").addEventListener("click", (event) => {
|
|
1936
|
-
const button = event.target.closest("[data-collection]")
|
|
1937
|
-
if (!button) return
|
|
1938
|
-
state.collection = button.dataset.collection
|
|
1939
|
-
renderState().catch(showError)
|
|
1940
|
-
})
|
|
1941
|
-
$("records").addEventListener("click", (event) => {
|
|
1942
|
-
const row = event.target.closest("[data-id]")
|
|
1943
|
-
if (!row) return
|
|
1944
|
-
openEditor(row.dataset.id).catch(showError)
|
|
1945
|
-
})
|
|
1946
|
-
|
|
1947
|
-
const editor = $("editor")
|
|
1948
|
-
async function openEditor(id) {
|
|
1949
|
-
state.editing = id || null
|
|
1950
|
-
$("editor-title").textContent = id ? "Edit record" : "New record"
|
|
1951
|
-
$("editor-id").value = id || ""
|
|
1952
|
-
$("editor-id").readOnly = Boolean(id)
|
|
1953
|
-
$("editor-delete").hidden = !id
|
|
1954
|
-
if (id) {
|
|
1955
|
-
const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
1956
|
-
$("editor-value").value = JSON.stringify(record.value, null, 2)
|
|
1957
|
-
} else {
|
|
1958
|
-
$("editor-value").value = "{\\n \\n}"
|
|
1959
|
-
}
|
|
1960
|
-
editor.showModal()
|
|
1961
|
-
}
|
|
1962
|
-
$("new-record").addEventListener("click", () => openEditor(null).catch(showError))
|
|
1963
|
-
$("editor-form").addEventListener("submit", (event) => {
|
|
1964
|
-
const submitter = event.submitter
|
|
1965
|
-
if (!submitter || submitter.value !== "save") return
|
|
1966
|
-
event.preventDefault()
|
|
1967
|
-
let value
|
|
1968
|
-
try { value = JSON.parse($("editor-value").value) }
|
|
1969
|
-
catch { showError(new Error("Value is not JSON")); return }
|
|
1970
|
-
const id = $("editor-id").value.trim()
|
|
1971
|
-
const path = "/state/" + encodeURIComponent(state.collection)
|
|
1972
|
-
const request = state.editing
|
|
1973
|
-
? api("PUT", path + "/" + encodeURIComponent(id), { value })
|
|
1974
|
-
: api("POST", path, { id: id || undefined, value })
|
|
1975
|
-
request.then(() => { editor.close(); return renderState() }).catch(showError)
|
|
1976
|
-
})
|
|
1977
|
-
$("editor-delete").addEventListener("click", () => {
|
|
1978
|
-
const id = state.editing
|
|
1979
|
-
if (!id) return
|
|
1980
|
-
api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
1981
|
-
.then(() => { editor.close(); return renderState() })
|
|
1982
|
-
.catch(showError)
|
|
1983
|
-
})
|
|
1984
|
-
|
|
1985
|
-
document.querySelectorAll("[data-advance]").forEach((button) => {
|
|
1986
|
-
button.addEventListener("click", () => {
|
|
1987
|
-
api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
|
|
1988
|
-
})
|
|
1989
|
-
})
|
|
1990
|
-
$("freeze").addEventListener("click", () => {
|
|
1991
|
-
const frozen = $("freeze").dataset.frozen === "1"
|
|
1992
|
-
api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
|
|
1993
|
-
})
|
|
1994
|
-
$("clock-reset").addEventListener("click", () => {
|
|
1995
|
-
api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
|
|
1996
|
-
})
|
|
1997
|
-
$("clock-set").addEventListener("submit", (event) => {
|
|
1998
|
-
event.preventDefault()
|
|
1999
|
-
const raw = $("clock-instant").value.trim()
|
|
2000
|
-
const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
|
|
2001
|
-
api("POST", "/clock", { set }).then(renderClock).catch(showError)
|
|
2002
|
-
})
|
|
2003
|
-
|
|
2004
|
-
$("fault-form").addEventListener("submit", (event) => {
|
|
2005
|
-
event.preventDefault()
|
|
2006
|
-
const preset = $("fault-preset").value
|
|
2007
|
-
const body = preset
|
|
2008
|
-
? { preset }
|
|
2009
|
-
: {
|
|
2010
|
-
status: Number($("fault-status").value),
|
|
2011
|
-
...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
|
|
2012
|
-
...($("fault-method").value ? { method: $("fault-method").value } : {})
|
|
2013
|
-
}
|
|
2014
|
-
if (!preset && !Number.isFinite(body.status)) {
|
|
2015
|
-
showError(new Error("Status must be a number, or choose a preset"))
|
|
2016
|
-
return
|
|
2017
|
-
}
|
|
2018
|
-
api("POST", "/faults", body).then(renderFaults).catch(showError)
|
|
2019
|
-
})
|
|
2020
|
-
$("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
|
|
2021
|
-
$("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
|
|
2022
|
-
|
|
2023
|
-
$("route-list").addEventListener("click", (event) => {
|
|
2024
|
-
const button = event.target.closest("[data-route]")
|
|
2025
|
-
if (!button) return
|
|
2026
|
-
state.route = button.dataset.route
|
|
2027
|
-
$("route-title").textContent = state.route
|
|
2028
|
-
const method = state.route.split(" ")[0]
|
|
2029
|
-
$("route-body").disabled = method === "GET" || method === "DELETE"
|
|
2030
|
-
})
|
|
2031
|
-
$("route-form").addEventListener("submit", async (event) => {
|
|
2032
|
-
event.preventDefault()
|
|
2033
|
-
if (!state.route) return showError(new Error("Choose a route first"))
|
|
2034
|
-
const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
|
|
2035
|
-
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
|
|
2036
|
-
let body
|
|
2037
|
-
if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
|
|
2038
|
-
try { body = JSON.parse($("route-body").value) }
|
|
2039
|
-
catch { return showError(new Error("Body is not JSON")) }
|
|
2040
|
-
}
|
|
2041
|
-
try {
|
|
2042
|
-
const data = await api(method, concrete, body)
|
|
2043
|
-
$("route-result").textContent = JSON.stringify(data, null, 2)
|
|
2044
|
-
} catch (error) {
|
|
2045
|
-
showError(error)
|
|
2046
|
-
}
|
|
2047
|
-
})
|
|
2048
|
-
|
|
2049
|
-
function mountPanels(panels) {
|
|
2050
|
-
const nav = $("nav")
|
|
2051
|
-
const views = $("views")
|
|
2052
|
-
for (const panel of panels) {
|
|
2053
|
-
const button = document.createElement("button")
|
|
2054
|
-
button.type = "button"
|
|
2055
|
-
button.dataset.view = panel.id
|
|
2056
|
-
button.textContent = panel.title
|
|
2057
|
-
nav.append(button)
|
|
2058
|
-
const section = document.createElement("section")
|
|
2059
|
-
section.className = "view"
|
|
2060
|
-
section.id = "view-" + panel.id
|
|
2061
|
-
section.hidden = true
|
|
2062
|
-
const title = document.createElement("h2")
|
|
2063
|
-
title.textContent = panel.title
|
|
2064
|
-
const lede = document.createElement("p")
|
|
2065
|
-
lede.className = "lede"
|
|
2066
|
-
lede.textContent = panel.description || ""
|
|
2067
|
-
const body = document.createElement("div")
|
|
2068
|
-
body.className = "panel-body"
|
|
2069
|
-
body.id = "panel-" + panel.id
|
|
2070
|
-
body.innerHTML = panel.html || ""
|
|
2071
|
-
section.append(title, lede, body)
|
|
2072
|
-
views.append(section)
|
|
2073
|
-
if (panel.script) {
|
|
2074
|
-
try {
|
|
2075
|
-
const run = new Function("root", "api", panel.script)
|
|
2076
|
-
run(body, panelApi)
|
|
2077
|
-
} catch (error) {
|
|
2078
|
-
const note = document.createElement("p")
|
|
2079
|
-
note.className = "banner show"
|
|
2080
|
-
note.textContent = error instanceof Error ? error.message : String(error)
|
|
2081
|
-
body.append(note)
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
loadNamespaces()
|
|
2088
|
-
.then(() => api("GET", "/ui/manifest"))
|
|
2089
|
-
.then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
|
|
2090
|
-
.catch(showError)
|
|
2091
|
-
})()
|
|
2092
|
-
</script>
|
|
2893
|
+
${script}
|
|
2093
2894
|
</body>
|
|
2094
2895
|
</html>`;
|
|
2095
2896
|
};
|
|
@@ -2106,10 +2907,10 @@ var json3 = (body) => new Response(JSON.stringify(body), {
|
|
|
2106
2907
|
});
|
|
2107
2908
|
var adminUiRoutes = (service, ui) => {
|
|
2108
2909
|
const shell = () => renderAdminDocument(service);
|
|
2109
|
-
const
|
|
2910
|
+
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
2110
2911
|
return {
|
|
2111
|
-
"GET /ui": () => htmlResponse(
|
|
2112
|
-
"GET /ui/": () => htmlResponse(
|
|
2912
|
+
"GET /ui": () => htmlResponse(document3()),
|
|
2913
|
+
"GET /ui/": () => htmlResponse(document3()),
|
|
2113
2914
|
"GET /ui/manifest": () => json3({
|
|
2114
2915
|
service,
|
|
2115
2916
|
panels: (ui?.panels ?? []).map((panel) => ({
|
|
@@ -2135,11 +2936,11 @@ var EXTENSION_KEYS = {
|
|
|
2135
2936
|
};
|
|
2136
2937
|
|
|
2137
2938
|
// ../../openapi/metadata/dist/read.js
|
|
2138
|
-
var
|
|
2939
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2139
2940
|
var extensionOf = (holder, key) => holder[key];
|
|
2140
2941
|
var operationMetadata = (operation) => {
|
|
2141
2942
|
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
2142
|
-
const ext =
|
|
2943
|
+
const ext = isRecord3(raw) ? raw : {};
|
|
2143
2944
|
const supported = ext.supported ?? true;
|
|
2144
2945
|
const parity = ext.parity ?? {};
|
|
2145
2946
|
return {
|
|
@@ -2175,9 +2976,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
2175
2976
|
this.name = "OperationRegistryError";
|
|
2176
2977
|
}
|
|
2177
2978
|
};
|
|
2178
|
-
var verifyOperations = (
|
|
2979
|
+
var verifyOperations = (document3, handlers) => {
|
|
2179
2980
|
const problems = [];
|
|
2180
|
-
const operations = listOperations(
|
|
2981
|
+
const operations = listOperations(document3);
|
|
2181
2982
|
const seen = /* @__PURE__ */ new Set();
|
|
2182
2983
|
for (const operation of operations) {
|
|
2183
2984
|
if (seen.has(operation.operationId))
|
|
@@ -2293,7 +3094,7 @@ var SAMPLE = 25;
|
|
|
2293
3094
|
var WALK_LIMIT = 1e4;
|
|
2294
3095
|
var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
|
|
2295
3096
|
var fail = (status, message) => ({ ok: false, status, message });
|
|
2296
|
-
var
|
|
3097
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2297
3098
|
var kindOf = (value) => {
|
|
2298
3099
|
if (value === null)
|
|
2299
3100
|
return "null";
|
|
@@ -2392,7 +3193,7 @@ var absorb = (accum, value) => {
|
|
|
2392
3193
|
const kind = kindOf(value);
|
|
2393
3194
|
accum.kinds.add(kind);
|
|
2394
3195
|
accum.present++;
|
|
2395
|
-
if (!
|
|
3196
|
+
if (!isRecord4(value))
|
|
2396
3197
|
return;
|
|
2397
3198
|
for (const [key, child] of Object.entries(value)) {
|
|
2398
3199
|
const nested = accum.nested.get(key) ?? blankAccum(key);
|
|
@@ -2417,7 +3218,7 @@ var inferFields = (records) => {
|
|
|
2417
3218
|
if (records.length === 0)
|
|
2418
3219
|
return [];
|
|
2419
3220
|
const sample = records.slice(0, SAMPLE);
|
|
2420
|
-
const objects = sample.filter((record) =>
|
|
3221
|
+
const objects = sample.filter((record) => isRecord4(record.value));
|
|
2421
3222
|
if (objects.length === 0) {
|
|
2422
3223
|
const kinds = new Set(sample.map((record) => kindOf(record.value)));
|
|
2423
3224
|
const list2 = [...kinds];
|
|
@@ -2604,7 +3405,7 @@ var patchStateRecord = (scope, name, id, value) => {
|
|
|
2604
3405
|
const parsed = jsonValue(value);
|
|
2605
3406
|
if (!parsed.ok)
|
|
2606
3407
|
return parsed;
|
|
2607
|
-
const next =
|
|
3408
|
+
const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
|
|
2608
3409
|
return replaceStateRecord(scope, name, id, next);
|
|
2609
3410
|
};
|
|
2610
3411
|
var deleteStateRecord = (scope, name, id) => {
|
|
@@ -2672,7 +3473,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2672
3473
|
const scope = open(namespace);
|
|
2673
3474
|
if (!scope.ok)
|
|
2674
3475
|
return fromResult(scope, 201);
|
|
2675
|
-
if (!
|
|
3476
|
+
if (!isRecord4(body) || !("value" in body)) {
|
|
2676
3477
|
return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
|
|
2677
3478
|
}
|
|
2678
3479
|
const id = body.id;
|
|
@@ -2690,7 +3491,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2690
3491
|
const scope = open(namespace);
|
|
2691
3492
|
if (!scope.ok)
|
|
2692
3493
|
return fromResult(scope, 200);
|
|
2693
|
-
if (!
|
|
3494
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2694
3495
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2695
3496
|
const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2696
3497
|
if (replaced.ok)
|
|
@@ -2701,7 +3502,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2701
3502
|
const scope = open(namespace);
|
|
2702
3503
|
if (!scope.ok)
|
|
2703
3504
|
return fromResult(scope, 200);
|
|
2704
|
-
if (!
|
|
3505
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2705
3506
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2706
3507
|
const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2707
3508
|
if (patched.ok)
|
|
@@ -2721,14 +3522,14 @@ var stateAdminRoutes = (routes) => {
|
|
|
2721
3522
|
};
|
|
2722
3523
|
|
|
2723
3524
|
// ../core/dist/version.js
|
|
2724
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3525
|
+
var PACKAGE_VERSION = true ? "1.5.0" : UNRELEASED_VERSION;
|
|
2725
3526
|
|
|
2726
3527
|
// ../core/dist/webhooks.js
|
|
2727
3528
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2728
3529
|
var adminError3 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
2729
|
-
var
|
|
3530
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2730
3531
|
var parseEndpoint = (value) => {
|
|
2731
|
-
if (!
|
|
3532
|
+
if (!isRecord5(value) || typeof value.url !== "string")
|
|
2732
3533
|
return "each endpoint needs a url";
|
|
2733
3534
|
try {
|
|
2734
3535
|
new URL(value.url);
|
|
@@ -2745,12 +3546,12 @@ var parseEndpoint = (value) => {
|
|
|
2745
3546
|
const events = value.events ?? value.enabledEvents;
|
|
2746
3547
|
if (Array.isArray(events))
|
|
2747
3548
|
endpoint.events = events.map(String);
|
|
2748
|
-
if (
|
|
3549
|
+
if (isRecord5(value.tags)) {
|
|
2749
3550
|
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
2750
3551
|
}
|
|
2751
3552
|
if (typeof value.account === "string")
|
|
2752
3553
|
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
2753
|
-
if (
|
|
3554
|
+
if (isRecord5(value.headers)) {
|
|
2754
3555
|
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
2755
3556
|
}
|
|
2756
3557
|
return endpoint;
|
|
@@ -2777,7 +3578,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
2777
3578
|
return json5(200, { status: "ok" });
|
|
2778
3579
|
},
|
|
2779
3580
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
2780
|
-
if (!
|
|
3581
|
+
if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
2781
3582
|
return adminError3(400, "mode must be duplicate, reorder or drop");
|
|
2782
3583
|
}
|
|
2783
3584
|
const fault = { mode: body.mode };
|
|
@@ -2793,7 +3594,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
2793
3594
|
}))
|
|
2794
3595
|
}),
|
|
2795
3596
|
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
2796
|
-
const list2 = Array.isArray(body) ? body :
|
|
3597
|
+
const list2 = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
|
|
2797
3598
|
if (!Array.isArray(list2))
|
|
2798
3599
|
return adminError3(400, "expected [{url, secret?, events?}]");
|
|
2799
3600
|
const parsed = [];
|
|
@@ -2863,8 +3664,8 @@ var DroppedConnectionError = class extends TypeError {
|
|
|
2863
3664
|
this.name = "TypeError";
|
|
2864
3665
|
}
|
|
2865
3666
|
};
|
|
2866
|
-
var operationMatcher = (
|
|
2867
|
-
const matchers = listOperations(
|
|
3667
|
+
var operationMatcher = (document3) => {
|
|
3668
|
+
const matchers = listOperations(document3).map((operation) => ({
|
|
2868
3669
|
operationId: operation.operationId,
|
|
2869
3670
|
method: operation.method.toUpperCase(),
|
|
2870
3671
|
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
@@ -2959,10 +3760,10 @@ var createRuntime = (options) => {
|
|
|
2959
3760
|
const ensureBranch = (namespace, branch2, at) => {
|
|
2960
3761
|
if (!BRANCH_PATTERN2.test(branch2))
|
|
2961
3762
|
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
2962
|
-
const
|
|
3763
|
+
const history2 = timeline(namespace);
|
|
2963
3764
|
if (branch2 === "main") {
|
|
2964
3765
|
if (at !== void 0) {
|
|
2965
|
-
const point =
|
|
3766
|
+
const point = history2.checkout("main", at);
|
|
2966
3767
|
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
2967
3768
|
captured.set(namespace, point.value.snapshot);
|
|
2968
3769
|
rng.setState(point.value.rngState);
|
|
@@ -2975,10 +3776,10 @@ var createRuntime = (options) => {
|
|
|
2975
3776
|
return namespace;
|
|
2976
3777
|
}
|
|
2977
3778
|
const storage = physicalBranch(namespace, branch2);
|
|
2978
|
-
if (!
|
|
3779
|
+
if (!history2.hasBranch(branch2)) {
|
|
2979
3780
|
if (at === void 0)
|
|
2980
|
-
|
|
2981
|
-
const point =
|
|
3781
|
+
history2.commit(capture(namespace));
|
|
3782
|
+
const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
|
|
2982
3783
|
const branchRng = createRng(options.seed ?? 0);
|
|
2983
3784
|
if (point)
|
|
2984
3785
|
branchRng.setState(point.value.rngState);
|
|
@@ -2987,8 +3788,8 @@ var createRuntime = (options) => {
|
|
|
2987
3788
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
2988
3789
|
if (point)
|
|
2989
3790
|
captured.set(storage, point.value.snapshot);
|
|
2990
|
-
} else if (at !== void 0 &&
|
|
2991
|
-
const point =
|
|
3791
|
+
} else if (at !== void 0 && history2.head(branch2)?.id !== at) {
|
|
3792
|
+
const point = history2.checkout(branch2, at);
|
|
2992
3793
|
if (!instances.has(storage)) {
|
|
2993
3794
|
const branchRng = createRng(options.seed ?? 0);
|
|
2994
3795
|
branchRng.setState(point.value.rngState);
|
|
@@ -2999,7 +3800,7 @@ var createRuntime = (options) => {
|
|
|
2999
3800
|
captured.set(storage, point.value.snapshot);
|
|
3000
3801
|
} else {
|
|
3001
3802
|
if (!instances.has(storage)) {
|
|
3002
|
-
const point =
|
|
3803
|
+
const point = history2.head(branch2);
|
|
3003
3804
|
const branchRng = createRng(options.seed ?? 0);
|
|
3004
3805
|
if (point)
|
|
3005
3806
|
branchRng.setState(point.value.rngState);
|
|
@@ -3023,8 +3824,8 @@ var createRuntime = (options) => {
|
|
|
3023
3824
|
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
3024
3825
|
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
3025
3826
|
const branchName = checkoutOptions.branch ?? "main";
|
|
3026
|
-
const
|
|
3027
|
-
const point =
|
|
3827
|
+
const history2 = timeline(namespace);
|
|
3828
|
+
const point = history2.checkout(branchName, checkpointId);
|
|
3028
3829
|
const storage = ensureBranch(namespace, branchName);
|
|
3029
3830
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3030
3831
|
captured.set(storage, point.value.snapshot);
|
|
@@ -3074,9 +3875,9 @@ var createRuntime = (options) => {
|
|
|
3074
3875
|
instance(name);
|
|
3075
3876
|
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
3076
3877
|
captured.set(name, from);
|
|
3077
|
-
const
|
|
3078
|
-
if (
|
|
3079
|
-
|
|
3878
|
+
const history2 = timelines.get(name);
|
|
3879
|
+
if (history2)
|
|
3880
|
+
history2.commit(capture(name), { branch: "main" });
|
|
3080
3881
|
else
|
|
3081
3882
|
timeline(name);
|
|
3082
3883
|
};
|
|
@@ -3305,10 +4106,10 @@ var createRuntime = (options) => {
|
|
|
3305
4106
|
},
|
|
3306
4107
|
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
3307
4108
|
inspect: (name) => {
|
|
3308
|
-
const
|
|
4109
|
+
const history2 = timeline(name);
|
|
3309
4110
|
return {
|
|
3310
|
-
branches:
|
|
3311
|
-
checkpoints:
|
|
4111
|
+
branches: history2.branches(),
|
|
4112
|
+
checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
3312
4113
|
id,
|
|
3313
4114
|
branch: branchName,
|
|
3314
4115
|
parent,
|
|
@@ -3323,11 +4124,11 @@ var createRuntime = (options) => {
|
|
|
3323
4124
|
} : {},
|
|
3324
4125
|
routes: {
|
|
3325
4126
|
...credentialRoutes(credentials),
|
|
3326
|
-
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3327
4127
|
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
3328
4128
|
...options.admin?.(runtime) ?? {},
|
|
3329
|
-
//
|
|
3330
|
-
//
|
|
4129
|
+
// Shell routes win a colliding key. Fault presets are one of them: the admin page
|
|
4130
|
+
// maps `presets`, and a service route must not replace that list with a record.
|
|
4131
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3331
4132
|
...stateAdminRoutes({
|
|
3332
4133
|
open: stateScope,
|
|
3333
4134
|
afterWrite: (namespace) => {
|
|
@@ -3402,9 +4203,12 @@ var credentialRoutes = (registry) => ({
|
|
|
3402
4203
|
}
|
|
3403
4204
|
});
|
|
3404
4205
|
var presetRoutes = (presets, runtime) => ({
|
|
3405
|
-
"GET /faults/presets": () =>
|
|
3406
|
-
|
|
3407
|
-
|
|
4206
|
+
"GET /faults/presets": () => {
|
|
4207
|
+
const body = {
|
|
4208
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
4209
|
+
};
|
|
4210
|
+
return adminJson(200, body);
|
|
4211
|
+
},
|
|
3408
4212
|
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
3409
4213
|
const name = params.name;
|
|
3410
4214
|
if (!presets[name])
|
|
@@ -3415,7 +4219,7 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
3415
4219
|
});
|
|
3416
4220
|
|
|
3417
4221
|
// src/generated/openapi.ts
|
|
3418
|
-
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Slack incoming webhooks and Web API (Mockingbird subset)","description":"Stateful mock subset of Slack: incoming webhooks (\`POST /services/T/B/X\`) and the Web API\\nmethods our apps call. Every message lands in an outbox a suite can assert on. Hand-authored\\nfrom Slack's documented wire shapes and the consumers (backend \`postSlackWebhook\`, EMR\\n\`slack-web-api.ts\`, request-intake \`SlackClient\`, release-conductor).\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from https://api.slack.com/methods and https://api.slack.com/messaging/webhooks (Slack publishes no maintained OpenAPI for these methods)."}},"servers":[{"url":"https://slack.com"},{"url":"https://hooks.slack.com"}],"security":[{"bearerAuth":[]}],"paths":{"/services/{team}/{bot}/{secret}":{"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","pattern":"^T[A-Z0-9]{2,12}$"}},{"name":"bot","in":"path","required":true,"schema":{"type":"string","pattern":"^B[A-Z0-9]{2,12}$"}},{"name":"secret","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9]{8,24}$"}}],"post":{"operationId":"PostIncomingWebhook","summary":"Incoming webhook: post a message to the hook's channel","description":"Answers \`200 ok\` (text/plain). Bad payloads get Slack's plain-text error codes (\`invalid_payload\`, \`no_text\`, \`invalid_blocks\`); an unregistered hook is \`404 no_service\` once any hook is registered. Also accepts form bodies with a \`payload=<json>\` field.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24},"username":{"type":"string","maxLength":40},"icon_emoji":{"type":"string","maxLength":40},"unfurl_links":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Posted","content":{"text/plain":{"schema":{"type":"string","enum":["ok"]}}}},"400":{"description":"\`invalid_payload\`, \`no_text\`, \`invalid_blocks\`, \`too_many_attachments\`","content":{"text/plain":{"schema":{"type":"string"}}}},"403":{"description":"\`action_prohibited\`, \`posting_to_general_channel_denied\`","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"\`no_service\`, \`channel_not_found\`","content":{"text/plain":{"schema":{"type":"string"}}}},"410":{"description":"\`channel_is_archived\`","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"description":"\`rate_limited\`, with \`retry-after\`","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"\`internal_error\`","content":{"text/plain":{"schema":{"type":"string"}}}},"503":{"description":"\`service_unavailable\`","content":{"text/plain":{"schema":{"type":"string"}}}}},"security":[]}},"/api/chat.postMessage":{"post":{"operationId":"ChatPostMessage","summary":"chat.postMessage: post a message (or a thread reply) to a channel","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"text":{"type":"string","maxLength":400},"blocks":{"type":"string","description":"JSON-encoded blocks","maxLength":2000},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"mrkdwn":{"type":"boolean"},"unfurl_links":{"type":"boolean"}},"required":["channel"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostMessageResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.update":{"post":{"operationId":"ChatUpdate","summary":"chat.update: edit a message the bot posted","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"text":{"type":"string","maxLength":400}},"required":["channel","ts"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}}},"required":["channel","ts"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.postEphemeral":{"post":{"operationId":"ChatPostEphemeral","summary":"chat.postEphemeral: a message only one user sees","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}},"text":{"type":"string","maxLength":400}},"required":["channel","user"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","user"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EphemeralResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.getPermalink":{"get":{"operationId":"ChatGetPermalinkGet","summary":"chat.getPermalink (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},{"name":"message_ts","in":"query","required":false,"schema":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermalinkResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"ChatGetPermalink","summary":"chat.getPermalink: the permalink of a message","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"message_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","message_ts"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"message_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","message_ts"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermalinkResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.add":{"post":{"operationId":"ReactionsAdd","summary":"reactions.add: react to a message","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["channel","timestamp","name"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["channel","timestamp","name"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.remove":{"post":{"operationId":"ReactionsRemove","summary":"reactions.remove: take a reaction back","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["name"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["name"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.get":{"get":{"operationId":"ReactionsGetGet","summary":"reactions.get (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},{"name":"timestamp","in":"query","required":false,"schema":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},{"name":"full","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionsGetResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"ReactionsGet","summary":"reactions.get: a message and its reactions","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"full":{"type":"string","enum":["true","false"]}},"required":["channel","timestamp"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"full":{"type":"boolean"}},"required":["channel","timestamp"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionsGetResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/auth.test":{"get":{"operationId":"AuthTestGet","summary":"auth.test (GET)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTestResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"AuthTest","summary":"auth.test: who the token belongs to","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{}}},"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTestResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/conversations.join":{"post":{"operationId":"ConversationsJoin","summary":"conversations.join: the bot joins a public channel","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},"required":["channel"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},"required":["channel"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/users.info":{"get":{"operationId":"UsersInfoGet","summary":"users.info (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"user","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"UsersInfo","summary":"users.info: a user's profile","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}},"required":["user"]}},"application/json":{"schema":{"type":"object","properties":{"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}},"required":["user"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/users.lookupByEmail":{"get":{"operationId":"UsersLookupByEmailGet","summary":"users.lookupByEmail (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","format":"email","maxLength":80}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"UsersLookupByEmail","summary":"users.lookupByEmail: find a user by email","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":80}},"required":["email"]}},"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":80}},"required":["email"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/files.info":{"get":{"operationId":"FilesInfoGet","summary":"files.info (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"file","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"FilesInfo","summary":"files.info: a file's metadata and private download URL","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}},"required":["file"]}},"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}},"required":["file"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/views.open":{"post":{"operationId":"ViewsOpen","summary":"views.open: open a modal for a trigger","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"trigger_id":{"type":"string","minLength":1,"maxLength":40},"view":{"type":"string","description":"JSON-encoded view","maxLength":2000}},"required":["trigger_id","view"]}},"application/json":{"schema":{"type":"object","properties":{"trigger_id":{"type":"string","minLength":1,"maxLength":40},"view":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["modal","home"]},"callback_id":{"type":"string","maxLength":40},"title":{"type":"object","properties":{"type":{"type":"string","enum":["plain_text"]},"text":{"type":"string","maxLength":24}}},"blocks":{"type":"array","maxItems":3,"items":{"type":"object"}},"private_metadata":{"type":"string","maxLength":200}}}},"required":["trigger_id","view"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Envelope":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"warning":{"type":"string"},"needed":{"type":"string"},"provided":{"type":"string"},"response_metadata":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}}}}}},"ErrorEnvelope":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string"}}},"Message":{"type":"object","required":["type","ts"],"properties":{"type":{"type":"string","enum":["message"]},"subtype":{"type":"string"},"text":{"type":"string"},"user":{"type":"string"},"bot_id":{"type":"string"},"app_id":{"type":"string"},"team":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"thread_ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"blocks":{"type":"array","items":{"type":"object"}},"edited":{"type":"object","properties":{"user":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}}},"reactions":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"},"users":{"type":"array","items":{"type":"string"}}}}}}},"PostMessageResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"ts":{"type":"string","x-mockingbird-resource":{"type":"message","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"message":{"$ref":"#/components/schemas/Message"}}}]},"ReactionsGetResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"type":{"type":"string","enum":["message"]},"channel":{"type":"string"},"message":{"$ref":"#/components/schemas/Message"}}}]},"UpdateResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"text":{"type":"string"},"message":{"$ref":"#/components/schemas/Message"}}}]},"EphemeralResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"message_ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}}}]},"PermalinkResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"permalink":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}]},"AuthTestResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"url":{"type":"string"},"team":{"type":"string"},"user":{"type":"string"},"team_id":{"type":"string"},"user_id":{"type":"string"},"bot_id":{"type":"string"},"is_enterprise_install":{"type":"boolean"}}}]},"Channel":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"is_channel":{"type":"boolean"},"is_private":{"type":"boolean"},"is_archived":{"type":"boolean"},"is_member":{"type":"boolean"},"created":{"type":"integer"}}},"JoinResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}]},"User":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"user","identity":true}},"team_id":{"type":"string"},"name":{"type":"string"},"real_name":{"type":"string"},"deleted":{"type":"boolean"},"is_bot":{"type":"boolean"},"tz":{"type":"string"},"profile":{"type":"object","properties":{"email":{"type":"string"},"real_name":{"type":"string"},"display_name":{"type":"string"}}}}},"UserResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}]},"File":{"type":"object","required":["id"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"file","identity":true}},"name":{"type":"string"},"title":{"type":"string"},"mimetype":{"type":"string"},"filetype":{"type":"string"},"size":{"type":"integer"},"created":{"type":"integer"},"url_private":{"type":"string"},"url_private_download":{"type":"string"},"permalink":{"type":"string"}}},"FileResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"file":{"$ref":"#/components/schemas/File"}}}]},"ViewResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"view":{"type":"object","properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"type":{"type":"string"},"team_id":{"type":"string"},"callback_id":{"type":"string"},"hash":{"type":"string","x-mockingbird-volatile":{"kind":"opaque"}},"blocks":{"type":"array","items":{"type":"object"}},"state":{"type":"object"},"private_metadata":{"type":"string"},"title":{"type":"object"},"app_id":{"type":"string"},"bot_id":{"type":"string"}}}}}]}}}}`);
|
|
4222
|
+
var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Slack incoming webhooks and Web API (Mockingbird subset)","description":"Stateful mock subset of Slack: incoming webhooks (\`POST /services/T/B/X\`) and the Web API\\nmethods our apps call. Every message lands in an outbox a suite can assert on. Hand-authored\\nfrom Slack's documented wire shapes and the consumers (backend \`postSlackWebhook\`, EMR\\n\`slack-web-api.ts\`, request-intake \`SlackClient\`, release-conductor).\\n","version":"1","x-mockingbird-upstream":{"note":"Hand-authored from https://api.slack.com/methods and https://api.slack.com/messaging/webhooks (Slack publishes no maintained OpenAPI for these methods)."}},"servers":[{"url":"https://slack.com"},{"url":"https://hooks.slack.com"}],"security":[{"bearerAuth":[]}],"paths":{"/services/{team}/{bot}/{secret}":{"parameters":[{"name":"team","in":"path","required":true,"schema":{"type":"string","pattern":"^T[A-Z0-9]{2,12}$"}},{"name":"bot","in":"path","required":true,"schema":{"type":"string","pattern":"^B[A-Z0-9]{2,12}$"}},{"name":"secret","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9]{8,24}$"}}],"post":{"operationId":"PostIncomingWebhook","summary":"Incoming webhook: post a message to the hook's channel","description":"Answers \`200 ok\` (text/plain). Bad payloads get Slack's plain-text error codes (\`invalid_payload\`, \`no_text\`, \`invalid_blocks\`); an unregistered hook is \`404 no_service\` once any hook is registered. Also accepts form bodies with a \`payload=<json>\` field.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24},"username":{"type":"string","maxLength":40},"icon_emoji":{"type":"string","maxLength":40},"unfurl_links":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Posted","content":{"text/plain":{"schema":{"type":"string","enum":["ok"]}}}},"400":{"description":"\`invalid_payload\`, \`no_text\`, \`invalid_blocks\`, \`too_many_attachments\`","content":{"text/plain":{"schema":{"type":"string"}}}},"403":{"description":"\`action_prohibited\`, \`posting_to_general_channel_denied\`","content":{"text/plain":{"schema":{"type":"string"}}}},"404":{"description":"\`no_service\`, \`channel_not_found\`","content":{"text/plain":{"schema":{"type":"string"}}}},"410":{"description":"\`channel_is_archived\`","content":{"text/plain":{"schema":{"type":"string"}}}},"429":{"description":"\`rate_limited\`, with \`retry-after\`","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"text/plain":{"schema":{"type":"string"}}}},"500":{"description":"\`internal_error\`","content":{"text/plain":{"schema":{"type":"string"}}}},"503":{"description":"\`service_unavailable\`","content":{"text/plain":{"schema":{"type":"string"}}}}},"security":[]}},"/api/chat.postMessage":{"post":{"operationId":"ChatPostMessage","summary":"chat.postMessage: post a message (or a thread reply) to a channel","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"text":{"type":"string","maxLength":400},"blocks":{"type":"string","description":"JSON-encoded blocks","maxLength":2000},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"mrkdwn":{"type":"boolean"},"unfurl_links":{"type":"boolean"}},"required":["channel"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostMessageResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.update":{"post":{"operationId":"ChatUpdate","summary":"chat.update: edit a message the bot posted","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"text":{"type":"string","maxLength":400}},"required":["channel","ts"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}}},"required":["channel","ts"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.postEphemeral":{"post":{"operationId":"ChatPostEphemeral","summary":"chat.postEphemeral: a message only one user sees","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}},"text":{"type":"string","maxLength":400}},"required":["channel","user"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}},"text":{"type":"string","maxLength":400},"blocks":{"type":"array","maxItems":3,"items":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["section","divider","header","context"]},"text":{"type":"object","properties":{"type":{"type":"string","enum":["mrkdwn","plain_text"]},"text":{"type":"string","maxLength":200}}}}}},"thread_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","user"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EphemeralResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/chat.getPermalink":{"get":{"operationId":"ChatGetPermalinkGet","summary":"chat.getPermalink (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},{"name":"message_ts","in":"query","required":false,"schema":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermalinkResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"ChatGetPermalink","summary":"chat.getPermalink: the permalink of a message","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"message_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","message_ts"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"message_ts":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},"required":["channel","message_ts"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermalinkResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.add":{"post":{"operationId":"ReactionsAdd","summary":"reactions.add: react to a message","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["channel","timestamp","name"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["channel","timestamp","name"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.remove":{"post":{"operationId":"ReactionsRemove","summary":"reactions.remove: take a reaction back","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["name"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"name":{"type":"string","enum":["eyes","white_check_mark","x","thumbsup","rotating_light"]}},"required":["name"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/reactions.get":{"get":{"operationId":"ReactionsGetGet","summary":"reactions.get (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"channel","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},{"name":"timestamp","in":"query","required":false,"schema":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}}},{"name":"full","in":"query","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionsGetResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"ReactionsGet","summary":"reactions.get: a message and its reactions","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"full":{"type":"string","enum":["true","false"]}},"required":["channel","timestamp"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"},"timestamp":{"type":"string","maxLength":24,"x-mockingbird-resource-ref":{"type":"message","missing":"1000000000.000000"}},"full":{"type":"boolean"}},"required":["channel","timestamp"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactionsGetResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/auth.test":{"get":{"operationId":"AuthTestGet","summary":"auth.test (GET)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTestResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"AuthTest","summary":"auth.test: who the token belongs to","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{}}},"application/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthTestResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/conversations.join":{"post":{"operationId":"ConversationsJoin","summary":"conversations.join: the bot joins a public channel","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},"required":["channel"]}},"application/json":{"schema":{"type":"object","properties":{"channel":{"type":"string","minLength":1,"maxLength":40,"description":"Channel id (\`C\u2026\`) or name (\`#alerts\`)"}},"required":["channel"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/users.info":{"get":{"operationId":"UsersInfoGet","summary":"users.info (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"user","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"UsersInfo","summary":"users.info: a user's profile","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}},"required":["user"]}},"application/json":{"schema":{"type":"object","properties":{"user":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"user","missing":"U00MISSING"}}},"required":["user"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/users.lookupByEmail":{"get":{"operationId":"UsersLookupByEmailGet","summary":"users.lookupByEmail (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"email","in":"query","required":false,"schema":{"type":"string","format":"email","maxLength":80}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"UsersLookupByEmail","summary":"users.lookupByEmail: find a user by email","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":80}},"required":["email"]}},"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":80}},"required":["email"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/files.info":{"get":{"operationId":"FilesInfoGet","summary":"files.info (query arguments)","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"parameters":[{"name":"file","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}}],"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"operationId":"FilesInfo","summary":"files.info: a file's metadata and private download URL","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}},"required":["file"]}},"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"maxLength":20,"x-mockingbird-resource-ref":{"type":"file","missing":"F00MISSING"}}},"required":["file"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FileResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/views.open":{"post":{"operationId":"ViewsOpen","summary":"views.open: open a modal for a trigger","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"requestBody":{"required":false,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object","properties":{"trigger_id":{"type":"string","minLength":1,"maxLength":40},"view":{"type":"string","description":"JSON-encoded view","maxLength":2000}},"required":["trigger_id","view"]}},"application/json":{"schema":{"type":"object","properties":{"trigger_id":{"type":"string","minLength":1,"maxLength":40},"view":{"type":"object","required":["type"],"properties":{"type":{"type":"string","enum":["modal","home"]},"callback_id":{"type":"string","maxLength":40},"title":{"type":"object","properties":{"type":{"type":"string","enum":["plain_text"]},"text":{"type":"string","maxLength":24}}},"blocks":{"type":"array","maxItems":3,"items":{"type":"object"}},"private_metadata":{"type":"string","maxLength":200}}}},"required":["trigger_id","view"]}}}},"responses":{"200":{"description":"Slack envelope: \`ok: true\` with the method's fields, or \`ok: false\` with an \`error\` code (Slack answers logical failures with HTTP 200)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ViewResponse"}}}},"429":{"description":"Rate limited (\`ratelimited\`), with \`retry-after\` seconds","headers":{"retry-after":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Slack is having trouble","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"Slack is unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Envelope":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean"},"error":{"type":"string"},"warning":{"type":"string"},"needed":{"type":"string"},"provided":{"type":"string"},"response_metadata":{"type":"object","properties":{"messages":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}}}}}},"ErrorEnvelope":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string"}}},"Message":{"type":"object","required":["type","ts"],"properties":{"type":{"type":"string","enum":["message"]},"subtype":{"type":"string"},"text":{"type":"string"},"user":{"type":"string"},"bot_id":{"type":"string"},"app_id":{"type":"string"},"team":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"thread_ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"blocks":{"type":"array","items":{"type":"object"}},"edited":{"type":"object","properties":{"user":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}}},"reactions":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"},"users":{"type":"array","items":{"type":"string"}}}}}}},"PostMessageResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"ts":{"type":"string","x-mockingbird-resource":{"type":"message","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"message":{"$ref":"#/components/schemas/Message"}}}]},"ReactionsGetResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"type":{"type":"string","enum":["message"]},"channel":{"type":"string"},"message":{"$ref":"#/components/schemas/Message"}}}]},"UpdateResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"text":{"type":"string"},"message":{"$ref":"#/components/schemas/Message"}}}]},"EphemeralResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"message_ts":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}}}]},"PermalinkResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"type":"string"},"permalink":{"type":"string","x-mockingbird-volatile":{"kind":"url"}}}}]},"AuthTestResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"url":{"type":"string"},"team":{"type":"string"},"user":{"type":"string"},"team_id":{"type":"string"},"user_id":{"type":"string"},"bot_id":{"type":"string"},"is_enterprise_install":{"type":"boolean"}}}]},"Channel":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"is_channel":{"type":"boolean"},"is_private":{"type":"boolean"},"is_archived":{"type":"boolean"},"is_member":{"type":"boolean"},"created":{"type":"integer"}}},"JoinResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"channel":{"$ref":"#/components/schemas/Channel"}}}]},"User":{"type":"object","required":["id","name"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"user","identity":true}},"team_id":{"type":"string"},"name":{"type":"string"},"real_name":{"type":"string"},"deleted":{"type":"boolean"},"is_bot":{"type":"boolean"},"tz":{"type":"string"},"profile":{"type":"object","properties":{"email":{"type":"string"},"real_name":{"type":"string"},"display_name":{"type":"string"}}}}},"UserResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"user":{"$ref":"#/components/schemas/User"}}}]},"File":{"type":"object","required":["id"],"properties":{"id":{"type":"string","x-mockingbird-resource":{"type":"file","identity":true}},"name":{"type":"string"},"title":{"type":"string"},"mimetype":{"type":"string"},"filetype":{"type":"string"},"size":{"type":"integer"},"created":{"type":"integer"},"url_private":{"type":"string"},"url_private_download":{"type":"string"},"permalink":{"type":"string"}}},"FileResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"file":{"$ref":"#/components/schemas/File"}}}]},"ViewResponse":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"view":{"type":"object","properties":{"id":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"type":{"type":"string"},"team_id":{"type":"string"},"callback_id":{"type":"string"},"hash":{"type":"string","x-mockingbird-volatile":{"kind":"opaque"}},"blocks":{"type":"array","items":{"type":"object"}},"state":{"type":"object"},"private_metadata":{"type":"string"},"title":{"type":"object"},"app_id":{"type":"string"},"bot_id":{"type":"string"}}}}}]}}}}`);
|
|
3419
4223
|
var operationIds = ["PostIncomingWebhook", "ChatPostMessage", "ChatUpdate", "ChatPostEphemeral", "ChatGetPermalinkGet", "ChatGetPermalink", "ReactionsAdd", "ReactionsRemove", "ReactionsGetGet", "ReactionsGet", "AuthTestGet", "AuthTest", "ConversationsJoin", "UsersInfoGet", "UsersInfo", "UsersLookupByEmailGet", "UsersLookupByEmail", "FilesInfoGet", "FilesInfo", "ViewsOpen"];
|
|
3420
4224
|
var supportedOperationIds = ["PostIncomingWebhook", "ChatPostMessage", "ChatUpdate", "ChatPostEphemeral", "ChatGetPermalinkGet", "ChatGetPermalink", "ReactionsAdd", "ReactionsRemove", "ReactionsGetGet", "ReactionsGet", "AuthTestGet", "AuthTest", "ConversationsJoin", "UsersInfoGet", "UsersInfo", "UsersLookupByEmailGet", "UsersLookupByEmail", "FilesInfoGet", "FilesInfo", "ViewsOpen"];
|
|
3421
4225
|
|
|
@@ -3580,8 +4384,8 @@ var SLACK_PRESETS = {
|
|
|
3580
4384
|
};
|
|
3581
4385
|
var json6 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3582
4386
|
var adminError4 = (status, message) => json6(status, { error: { type: "mockingbird_admin", message } });
|
|
3583
|
-
var
|
|
3584
|
-
var list = (body, key) => Array.isArray(body) ? body :
|
|
4387
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4388
|
+
var list = (body, key) => Array.isArray(body) ? body : isRecord6(body) && Array.isArray(body[key]) ? body[key] : void 0;
|
|
3585
4389
|
var hookSuffix = (value) => {
|
|
3586
4390
|
let path = value;
|
|
3587
4391
|
try {
|
|
@@ -3618,8 +4422,8 @@ var adminRoutes = (runtime) => ({
|
|
|
3618
4422
|
hooks: runtime.instance(namespace).state.hooks.list({ order: "oldest" }).map((row) => ({ ...row.value, url: `/services/${row.value.path}` }))
|
|
3619
4423
|
}),
|
|
3620
4424
|
"POST /hooks": ({ body, namespace }) => {
|
|
3621
|
-
const entries = list(body, "hooks") ?? (
|
|
3622
|
-
if (!entries || entries.some((e) => !
|
|
4425
|
+
const entries = list(body, "hooks") ?? (isRecord6(body) ? [body] : void 0);
|
|
4426
|
+
if (!entries || entries.some((e) => !isRecord6(e) || typeof e.path !== "string")) {
|
|
3623
4427
|
return adminError4(400, 'expected {"path": "T\u2026/B\u2026/X\u2026", "channel"?: "C\u2026"} or {"hooks": [...]}');
|
|
3624
4428
|
}
|
|
3625
4429
|
const api = runtime.instance(namespace);
|
|
@@ -3646,8 +4450,8 @@ var adminRoutes = (runtime) => ({
|
|
|
3646
4450
|
channels: runtime.instance(namespace).state.channels.list({ order: "oldest" }).map((r) => r.value)
|
|
3647
4451
|
}),
|
|
3648
4452
|
"POST /channels": ({ body, namespace }) => {
|
|
3649
|
-
const entries = list(body, "channels") ?? (
|
|
3650
|
-
if (!entries || entries.some((e) => !
|
|
4453
|
+
const entries = list(body, "channels") ?? (isRecord6(body) ? [body] : void 0);
|
|
4454
|
+
if (!entries || entries.some((e) => !isRecord6(e) || typeof e.name !== "string")) {
|
|
3651
4455
|
return adminError4(
|
|
3652
4456
|
400,
|
|
3653
4457
|
'expected {"id"?, "name", "is_private"?, "is_archived"?, "is_member"?}'
|
|
@@ -3673,8 +4477,8 @@ var adminRoutes = (runtime) => ({
|
|
|
3673
4477
|
users: runtime.instance(namespace).state.users.list({ order: "oldest" }).map((r) => r.value)
|
|
3674
4478
|
}),
|
|
3675
4479
|
"POST /users": ({ body, namespace }) => {
|
|
3676
|
-
const entries = list(body, "users") ?? (
|
|
3677
|
-
if (!entries || entries.some((e) => !
|
|
4480
|
+
const entries = list(body, "users") ?? (isRecord6(body) ? [body] : void 0);
|
|
4481
|
+
if (!entries || entries.some((e) => !isRecord6(e) || typeof e.id !== "string")) {
|
|
3678
4482
|
return adminError4(400, 'expected {"id": "U\u2026", "name"?, "real_name"?, "email"?}');
|
|
3679
4483
|
}
|
|
3680
4484
|
const api = runtime.instance(namespace);
|
|
@@ -3695,8 +4499,8 @@ var adminRoutes = (runtime) => ({
|
|
|
3695
4499
|
return json6(201, { users: saved });
|
|
3696
4500
|
},
|
|
3697
4501
|
"POST /files": ({ body, namespace }) => {
|
|
3698
|
-
const entries = list(body, "files") ?? (
|
|
3699
|
-
if (!entries || entries.some((e) => !
|
|
4502
|
+
const entries = list(body, "files") ?? (isRecord6(body) ? [body] : void 0);
|
|
4503
|
+
if (!entries || entries.some((e) => !isRecord6(e) || typeof e.id !== "string")) {
|
|
3700
4504
|
return adminError4(400, 'expected {"id": "F\u2026", "name"?, "mimetype"?, "size"?}');
|
|
3701
4505
|
}
|
|
3702
4506
|
const api = runtime.instance(namespace);
|
|
@@ -3722,7 +4526,7 @@ var adminRoutes = (runtime) => ({
|
|
|
3722
4526
|
}),
|
|
3723
4527
|
"GET /settings": ({ namespace }) => json6(200, runtime.instance(namespace).state.current()),
|
|
3724
4528
|
"PUT /settings": ({ body, namespace }) => {
|
|
3725
|
-
if (!
|
|
4529
|
+
if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
|
|
3726
4530
|
const patch = {};
|
|
3727
4531
|
for (const key of [
|
|
3728
4532
|
"teamId",
|
|
@@ -3750,7 +4554,7 @@ var adminRoutes = (runtime) => ({
|
|
|
3750
4554
|
});
|
|
3751
4555
|
var createRuntime2 = (options = {}) => createRuntime({
|
|
3752
4556
|
name: SLACK_NAMESPACE,
|
|
3753
|
-
document,
|
|
4557
|
+
document: document2,
|
|
3754
4558
|
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
3755
4559
|
...options.clock ? { clock: options.clock } : {},
|
|
3756
4560
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
@@ -3776,7 +4580,7 @@ var slackCredential = (request) => {
|
|
|
3776
4580
|
const path = new URL(request.url).pathname;
|
|
3777
4581
|
return WEBHOOK_PATH.exec(path.replace(/^\/ns\/[^/]+/, ""))?.[1];
|
|
3778
4582
|
};
|
|
3779
|
-
var
|
|
4583
|
+
var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3780
4584
|
var text = (status, body, headers = {}) => new Response(body, {
|
|
3781
4585
|
status,
|
|
3782
4586
|
headers: { "content-type": "text/plain; charset=utf-8", ...headers }
|
|
@@ -3799,7 +4603,7 @@ var readArgs = (context) => {
|
|
|
3799
4603
|
const contentType = context.request.headers.get("content-type") ?? "";
|
|
3800
4604
|
let json7 = false;
|
|
3801
4605
|
if (body.kind === "json") {
|
|
3802
|
-
if (!
|
|
4606
|
+
if (!isRecord7(body.value)) throw new SlackError("invalid_json");
|
|
3803
4607
|
Object.assign(args, body.value);
|
|
3804
4608
|
json7 = true;
|
|
3805
4609
|
} else if (body.kind === "form") {
|
|
@@ -3825,7 +4629,7 @@ var checkBlocks = (blocks) => {
|
|
|
3825
4629
|
if (blocks === void 0 || blocks === null || blocks === "") return null;
|
|
3826
4630
|
if (!Array.isArray(blocks) || blocks.length > 50) throw new SlackError("invalid_blocks");
|
|
3827
4631
|
for (const block of blocks) {
|
|
3828
|
-
if (!
|
|
4632
|
+
if (!isRecord7(block) || typeof block.type !== "string") throw new SlackError("invalid_blocks");
|
|
3829
4633
|
}
|
|
3830
4634
|
return blocks;
|
|
3831
4635
|
};
|
|
@@ -3882,7 +4686,7 @@ var SlackAPI = class {
|
|
|
3882
4686
|
ViewsOpen: api((args) => this.openView(args))
|
|
3883
4687
|
});
|
|
3884
4688
|
this.service = createService({
|
|
3885
|
-
document,
|
|
4689
|
+
document: document2,
|
|
3886
4690
|
handlers,
|
|
3887
4691
|
sqlite,
|
|
3888
4692
|
namespace,
|
|
@@ -4012,7 +4816,7 @@ var SlackAPI = class {
|
|
|
4012
4816
|
return text(400, "invalid_payload");
|
|
4013
4817
|
}
|
|
4014
4818
|
} else return text(400, "invalid_payload");
|
|
4015
|
-
if (!
|
|
4819
|
+
if (!isRecord7(payload)) return text(400, "invalid_payload");
|
|
4016
4820
|
let blocks;
|
|
4017
4821
|
try {
|
|
4018
4822
|
blocks = checkBlocks(payload.blocks);
|
|
@@ -4278,7 +5082,7 @@ var SlackAPI = class {
|
|
|
4278
5082
|
openView(args) {
|
|
4279
5083
|
if (!str(args.trigger_id)) throw new SlackError("invalid_arguments");
|
|
4280
5084
|
const view = args.view;
|
|
4281
|
-
if (!
|
|
5085
|
+
if (!isRecord7(view) || view.type !== "modal" && view.type !== "home") {
|
|
4282
5086
|
throw new SlackError("invalid_arguments", {
|
|
4283
5087
|
response_metadata: { messages: ["[ERROR] view.type must be modal"] }
|
|
4284
5088
|
});
|
|
@@ -4302,7 +5106,7 @@ var SlackAPI = class {
|
|
|
4302
5106
|
};
|
|
4303
5107
|
|
|
4304
5108
|
export {
|
|
4305
|
-
document,
|
|
5109
|
+
document2 as document,
|
|
4306
5110
|
operationIds,
|
|
4307
5111
|
supportedOperationIds,
|
|
4308
5112
|
DEFAULT_SETTINGS,
|
|
@@ -4315,4 +5119,4 @@ export {
|
|
|
4315
5119
|
slackCredential,
|
|
4316
5120
|
SlackAPI
|
|
4317
5121
|
};
|
|
4318
|
-
//# sourceMappingURL=chunk-
|
|
5122
|
+
//# sourceMappingURL=chunk-XLLGBYTB.js.map
|