@crvouga/mockingbird-service-plane 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/{chunk-UPZQ3IYS.js → chunk-JPPFFF6I.js} +2 -2
- package/dist/{chunk-CCLSLHDJ.js → chunk-X3BPHZRL.js} +1419 -615
- package/dist/chunk-X3BPHZRL.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-CCLSLHDJ.js.map +0 -7
- /package/dist/{chunk-UPZQ3IYS.js.map → chunk-JPPFFF6I.js.map} +0 -0
|
@@ -533,10 +533,10 @@ var OpenAPIReferenceError = class extends Error {
|
|
|
533
533
|
};
|
|
534
534
|
var unescapePointer = (segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
535
535
|
var isReference = (value) => typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
536
|
-
var resolveRef = (
|
|
536
|
+
var resolveRef = (document3, ref) => {
|
|
537
537
|
if (!ref.startsWith("#/"))
|
|
538
538
|
throw new OpenAPIReferenceError(ref);
|
|
539
|
-
let cursor =
|
|
539
|
+
let cursor = document3;
|
|
540
540
|
for (const raw of ref.slice(2).split("/")) {
|
|
541
541
|
const segment = unescapePointer(raw);
|
|
542
542
|
if (typeof cursor !== "object" || cursor === null || !(segment in cursor)) {
|
|
@@ -548,14 +548,14 @@ var resolveRef = (document2, ref) => {
|
|
|
548
548
|
throw new OpenAPIReferenceError(ref);
|
|
549
549
|
return cursor;
|
|
550
550
|
};
|
|
551
|
-
var deref = (
|
|
551
|
+
var deref = (document3, value) => {
|
|
552
552
|
let current = value;
|
|
553
553
|
const seen = /* @__PURE__ */ new Set();
|
|
554
554
|
while (isReference(current)) {
|
|
555
555
|
if (seen.has(current.$ref))
|
|
556
556
|
throw new OpenAPIReferenceError(`${current.$ref} (cycle)`);
|
|
557
557
|
seen.add(current.$ref);
|
|
558
|
-
current = resolveRef(
|
|
558
|
+
current = resolveRef(document3, current.$ref);
|
|
559
559
|
}
|
|
560
560
|
return current;
|
|
561
561
|
};
|
|
@@ -573,36 +573,36 @@ var HTTP_METHODS = [
|
|
|
573
573
|
];
|
|
574
574
|
|
|
575
575
|
// ../../openapi/core/dist/document.js
|
|
576
|
-
var mergeParameters = (
|
|
576
|
+
var mergeParameters = (document3, item, own) => {
|
|
577
577
|
const merged = /* @__PURE__ */ new Map();
|
|
578
578
|
for (const raw of item.parameters ?? []) {
|
|
579
|
-
const parameter = deref(
|
|
579
|
+
const parameter = deref(document3, raw);
|
|
580
580
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
581
581
|
}
|
|
582
582
|
for (const raw of own ?? []) {
|
|
583
|
-
const parameter = deref(
|
|
583
|
+
const parameter = deref(document3, raw);
|
|
584
584
|
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
585
585
|
}
|
|
586
586
|
return [...merged.values()];
|
|
587
587
|
};
|
|
588
|
-
var listOperations = (
|
|
588
|
+
var listOperations = (document3) => {
|
|
589
589
|
const operations = [];
|
|
590
|
-
for (const [path, item] of Object.entries(
|
|
590
|
+
for (const [path, item] of Object.entries(document3.paths)) {
|
|
591
591
|
for (const method of HTTP_METHODS) {
|
|
592
592
|
const operation = item[method];
|
|
593
593
|
if (operation?.operationId === void 0)
|
|
594
594
|
continue;
|
|
595
595
|
const responses = {};
|
|
596
596
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
597
|
-
responses[status] = deref(
|
|
597
|
+
responses[status] = deref(document3, response);
|
|
598
598
|
}
|
|
599
599
|
operations.push({
|
|
600
600
|
operationId: operation.operationId,
|
|
601
601
|
method,
|
|
602
602
|
path,
|
|
603
603
|
operation,
|
|
604
|
-
parameters: mergeParameters(
|
|
605
|
-
requestBody: operation.requestBody === void 0 ? void 0 : deref(
|
|
604
|
+
parameters: mergeParameters(document3, item, operation.parameters),
|
|
605
|
+
requestBody: operation.requestBody === void 0 ? void 0 : deref(document3, operation.requestBody),
|
|
606
606
|
responses
|
|
607
607
|
});
|
|
608
608
|
}
|
|
@@ -611,7 +611,7 @@ var listOperations = (document2) => {
|
|
|
611
611
|
};
|
|
612
612
|
|
|
613
613
|
// ../../openapi/core/dist/schema.js
|
|
614
|
-
var resolveSchema = (
|
|
614
|
+
var resolveSchema = (document3, schema) => {
|
|
615
615
|
let current = schema;
|
|
616
616
|
const seen = /* @__PURE__ */ new Set();
|
|
617
617
|
while (typeof current.$ref === "string") {
|
|
@@ -620,7 +620,7 @@ var resolveSchema = (document2, schema) => {
|
|
|
620
620
|
break;
|
|
621
621
|
seen.add(ref);
|
|
622
622
|
const { $ref: _ignored, ...siblings } = current;
|
|
623
|
-
const target = resolveRef(
|
|
623
|
+
const target = resolveRef(document3, ref);
|
|
624
624
|
current = { ...target, ...siblings };
|
|
625
625
|
}
|
|
626
626
|
if (current.nullable === true) {
|
|
@@ -691,9 +691,9 @@ var FORMAT_PATTERNS = {
|
|
|
691
691
|
ipv4: /^(25[0-5]|2[0-4]\d|1?\d?\d)(\.(25[0-5]|2[0-4]\d|1?\d?\d)){3}$/
|
|
692
692
|
};
|
|
693
693
|
var graphemeLength = (value) => [...value].length;
|
|
694
|
-
var validateValue = (
|
|
694
|
+
var validateValue = (document3, schema, value, path = []) => {
|
|
695
695
|
const errors = [];
|
|
696
|
-
const s = resolveSchema(
|
|
696
|
+
const s = resolveSchema(document3, schema);
|
|
697
697
|
const fail3 = (message) => errors.push({ path, message });
|
|
698
698
|
const actual = jsonTypeOf(value);
|
|
699
699
|
if (actual === "undefined") {
|
|
@@ -755,7 +755,7 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
755
755
|
value.forEach((item, i) => {
|
|
756
756
|
const itemSchema = s.prefixItems?.[i] ?? s.items;
|
|
757
757
|
if (itemSchema)
|
|
758
|
-
errors.push(...validateValue(
|
|
758
|
+
errors.push(...validateValue(document3, itemSchema, item, [...path, i]));
|
|
759
759
|
});
|
|
760
760
|
}
|
|
761
761
|
if (actual === "object") {
|
|
@@ -771,16 +771,16 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
771
771
|
for (const key of keys) {
|
|
772
772
|
const property = s.properties?.[key];
|
|
773
773
|
if (property) {
|
|
774
|
-
errors.push(...validateValue(
|
|
774
|
+
errors.push(...validateValue(document3, property, record[key], [...path, key]));
|
|
775
775
|
continue;
|
|
776
776
|
}
|
|
777
777
|
if (s.additionalProperties === false)
|
|
778
778
|
fail3(`unexpected property ${key}`);
|
|
779
779
|
else if (typeof s.additionalProperties === "object") {
|
|
780
|
-
errors.push(...validateValue(
|
|
780
|
+
errors.push(...validateValue(document3, s.additionalProperties, record[key], [...path, key]));
|
|
781
781
|
}
|
|
782
782
|
if (s.propertyNames) {
|
|
783
|
-
const nameErrors = validateValue(
|
|
783
|
+
const nameErrors = validateValue(document3, s.propertyNames, key, [...path, key]);
|
|
784
784
|
if (nameErrors.length > 0)
|
|
785
785
|
fail3(`property name ${key} is invalid: ${nameErrors[0]?.message}`);
|
|
786
786
|
}
|
|
@@ -788,15 +788,15 @@ var validateValue = (document2, schema, value, path = []) => {
|
|
|
788
788
|
}
|
|
789
789
|
if (s.allOf)
|
|
790
790
|
for (const branch of s.allOf)
|
|
791
|
-
errors.push(...validateValue(
|
|
792
|
-
if (s.anyOf && !s.anyOf.some((branch) => validateValue(
|
|
791
|
+
errors.push(...validateValue(document3, branch, value, path));
|
|
792
|
+
if (s.anyOf && !s.anyOf.some((branch) => validateValue(document3, branch, value).length === 0))
|
|
793
793
|
fail3("matches no anyOf branch");
|
|
794
794
|
if (s.oneOf) {
|
|
795
|
-
const matches2 = s.oneOf.filter((branch) => validateValue(
|
|
795
|
+
const matches2 = s.oneOf.filter((branch) => validateValue(document3, branch, value).length === 0).length;
|
|
796
796
|
if (matches2 !== 1)
|
|
797
797
|
fail3(`matches ${matches2} oneOf branches, expected exactly 1`);
|
|
798
798
|
}
|
|
799
|
-
if (s.not && validateValue(
|
|
799
|
+
if (s.not && validateValue(document3, s.not, value).length === 0)
|
|
800
800
|
fail3("matches forbidden `not` schema");
|
|
801
801
|
return errors;
|
|
802
802
|
};
|
|
@@ -1370,6 +1370,1232 @@ var clearNamespace = (sqlite, namespace) => {
|
|
|
1370
1370
|
});
|
|
1371
1371
|
};
|
|
1372
1372
|
|
|
1373
|
+
// ../../ui/dist/reset.js
|
|
1374
|
+
var CSS_RESET = `*,
|
|
1375
|
+
*::before,
|
|
1376
|
+
*::after {
|
|
1377
|
+
box-sizing: border-box;
|
|
1378
|
+
margin: 0;
|
|
1379
|
+
padding: 0;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
* {
|
|
1383
|
+
scrollbar-width: thin;
|
|
1384
|
+
scrollbar-color: color-mix(in srgb, CanvasText 35%, Canvas) Canvas;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
*::-webkit-scrollbar {
|
|
1388
|
+
width: 12px;
|
|
1389
|
+
height: 12px;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
*::-webkit-scrollbar-track,
|
|
1393
|
+
*::-webkit-scrollbar-corner {
|
|
1394
|
+
background: Canvas;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
*::-webkit-scrollbar-thumb {
|
|
1398
|
+
background: color-mix(in srgb, CanvasText 35%, Canvas);
|
|
1399
|
+
border: 3px solid Canvas;
|
|
1400
|
+
border-radius: 999px;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
html {
|
|
1404
|
+
hanging-punctuation: first allow-end last;
|
|
1405
|
+
-webkit-text-size-adjust: none;
|
|
1406
|
+
text-size-adjust: none;
|
|
1407
|
+
tab-size: 4;
|
|
1408
|
+
font-family:
|
|
1409
|
+
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI",
|
|
1410
|
+
"Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
1411
|
+
font-size: 16px;
|
|
1412
|
+
line-height: 1.5;
|
|
1413
|
+
-webkit-font-smoothing: antialiased;
|
|
1414
|
+
-moz-osx-font-smoothing: grayscale;
|
|
1415
|
+
text-rendering: optimizeLegibility;
|
|
1416
|
+
-webkit-tap-highlight-color: transparent;
|
|
1417
|
+
scroll-behavior: smooth;
|
|
1418
|
+
background: Canvas;
|
|
1419
|
+
color: CanvasText;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
body {
|
|
1423
|
+
min-height: 100svh;
|
|
1424
|
+
line-height: 1.5;
|
|
1425
|
+
background: Canvas;
|
|
1426
|
+
color: CanvasText;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
h1 {
|
|
1430
|
+
font-size: 2em;
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
h2 {
|
|
1434
|
+
font-size: 1.5em;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
h3 {
|
|
1438
|
+
font-size: 1.17em;
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
h4 {
|
|
1442
|
+
font-size: 1em;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
h5 {
|
|
1446
|
+
font-size: 0.83em;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
h6 {
|
|
1450
|
+
font-size: 0.67em;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
h1,
|
|
1454
|
+
h2,
|
|
1455
|
+
h3,
|
|
1456
|
+
h4,
|
|
1457
|
+
h5,
|
|
1458
|
+
h6 {
|
|
1459
|
+
font-weight: 700;
|
|
1460
|
+
line-height: 1.2;
|
|
1461
|
+
text-wrap: balance;
|
|
1462
|
+
overflow-wrap: break-word;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
p,
|
|
1466
|
+
li,
|
|
1467
|
+
figcaption,
|
|
1468
|
+
dd,
|
|
1469
|
+
dt,
|
|
1470
|
+
blockquote {
|
|
1471
|
+
overflow-wrap: break-word;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
p,
|
|
1475
|
+
li,
|
|
1476
|
+
figcaption,
|
|
1477
|
+
blockquote {
|
|
1478
|
+
text-wrap: pretty;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
ul,
|
|
1482
|
+
ol,
|
|
1483
|
+
menu {
|
|
1484
|
+
list-style: none;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
a {
|
|
1488
|
+
color: inherit;
|
|
1489
|
+
text-decoration: underline;
|
|
1490
|
+
text-decoration-thickness: 1px;
|
|
1491
|
+
text-underline-offset: 0.15em;
|
|
1492
|
+
text-decoration-skip-ink: auto;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
b,
|
|
1496
|
+
strong {
|
|
1497
|
+
font-weight: 700;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
small {
|
|
1501
|
+
font-size: 0.875em;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
code,
|
|
1505
|
+
kbd,
|
|
1506
|
+
samp,
|
|
1507
|
+
pre {
|
|
1508
|
+
font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1509
|
+
font-size: 1em;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
sub,
|
|
1513
|
+
sup {
|
|
1514
|
+
font-size: 75%;
|
|
1515
|
+
line-height: 0;
|
|
1516
|
+
position: relative;
|
|
1517
|
+
vertical-align: baseline;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
sub {
|
|
1521
|
+
bottom: -0.25em;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
sup {
|
|
1525
|
+
top: -0.5em;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
abbr[title] {
|
|
1529
|
+
text-decoration: underline dotted;
|
|
1530
|
+
text-underline-offset: 0.15em;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
mark {
|
|
1534
|
+
background: Mark;
|
|
1535
|
+
color: MarkText;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
hr {
|
|
1539
|
+
height: 0;
|
|
1540
|
+
border: 0;
|
|
1541
|
+
border-top: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1542
|
+
color: inherit;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
img,
|
|
1546
|
+
picture,
|
|
1547
|
+
video,
|
|
1548
|
+
canvas {
|
|
1549
|
+
display: block;
|
|
1550
|
+
max-width: 100%;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
img,
|
|
1554
|
+
video {
|
|
1555
|
+
height: auto;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
svg {
|
|
1559
|
+
overflow: hidden;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
iframe {
|
|
1563
|
+
border: 0;
|
|
1564
|
+
max-width: 100%;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
table {
|
|
1568
|
+
border-collapse: collapse;
|
|
1569
|
+
border-spacing: 0;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
fieldset {
|
|
1573
|
+
border: 0;
|
|
1574
|
+
min-inline-size: 0;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
legend {
|
|
1578
|
+
padding: 0;
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
button,
|
|
1582
|
+
input,
|
|
1583
|
+
select,
|
|
1584
|
+
textarea,
|
|
1585
|
+
optgroup {
|
|
1586
|
+
font: inherit;
|
|
1587
|
+
letter-spacing: inherit;
|
|
1588
|
+
color: inherit;
|
|
1589
|
+
margin: 0;
|
|
1590
|
+
vertical-align: middle;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
button,
|
|
1594
|
+
input[type="button"],
|
|
1595
|
+
input[type="submit"],
|
|
1596
|
+
input[type="reset"] {
|
|
1597
|
+
appearance: none;
|
|
1598
|
+
background: none;
|
|
1599
|
+
border: 0;
|
|
1600
|
+
padding: 0;
|
|
1601
|
+
cursor: pointer;
|
|
1602
|
+
text-align: center;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
input,
|
|
1606
|
+
textarea,
|
|
1607
|
+
select {
|
|
1608
|
+
background-color: Canvas;
|
|
1609
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1610
|
+
border-radius: 0;
|
|
1611
|
+
padding: 0.5em 0.65em;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
input[type="checkbox"],
|
|
1615
|
+
input[type="radio"],
|
|
1616
|
+
input[type="range"],
|
|
1617
|
+
input[type="file"],
|
|
1618
|
+
input[type="color"],
|
|
1619
|
+
input[type="button"],
|
|
1620
|
+
input[type="submit"],
|
|
1621
|
+
input[type="reset"],
|
|
1622
|
+
input[type="image"],
|
|
1623
|
+
input[type="hidden"] {
|
|
1624
|
+
background: none;
|
|
1625
|
+
border: 0;
|
|
1626
|
+
padding: 0;
|
|
1627
|
+
border-radius: 0;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
input[type="checkbox"],
|
|
1631
|
+
input[type="radio"] {
|
|
1632
|
+
accent-color: CanvasText;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
input[type="search"] {
|
|
1636
|
+
appearance: none;
|
|
1637
|
+
background-color: Canvas;
|
|
1638
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
input[type="search"]::-webkit-search-decoration,
|
|
1642
|
+
input[type="search"]::-webkit-search-cancel-button,
|
|
1643
|
+
input[type="search"]::-webkit-search-results-button,
|
|
1644
|
+
input[type="search"]::-webkit-search-results-decoration {
|
|
1645
|
+
-webkit-appearance: none;
|
|
1646
|
+
display: none;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
input[type="number"] {
|
|
1650
|
+
appearance: textfield;
|
|
1651
|
+
background-color: Canvas;
|
|
1652
|
+
border: 1px solid color-mix(in srgb, CanvasText 28%, transparent);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
input[type="number"]::-webkit-inner-spin-button,
|
|
1656
|
+
input[type="number"]::-webkit-outer-spin-button {
|
|
1657
|
+
-webkit-appearance: none;
|
|
1658
|
+
margin: 0;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
textarea {
|
|
1662
|
+
resize: vertical;
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
::placeholder {
|
|
1666
|
+
opacity: 1;
|
|
1667
|
+
color: color-mix(in srgb, CanvasText 45%, transparent);
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
:focus-visible {
|
|
1671
|
+
outline: 2px solid CanvasText;
|
|
1672
|
+
outline-offset: 2px;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
:focus:not(:focus-visible) {
|
|
1676
|
+
outline: none;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
:disabled,
|
|
1680
|
+
[disabled] {
|
|
1681
|
+
cursor: default;
|
|
1682
|
+
opacity: 0.6;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
label,
|
|
1686
|
+
summary,
|
|
1687
|
+
select,
|
|
1688
|
+
[role="button"] {
|
|
1689
|
+
cursor: pointer;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
summary {
|
|
1693
|
+
display: list-item;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
details {
|
|
1697
|
+
display: block;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
dialog {
|
|
1701
|
+
margin: auto;
|
|
1702
|
+
padding: 1rem;
|
|
1703
|
+
border: 1px solid color-mix(in srgb, CanvasText 20%, transparent);
|
|
1704
|
+
background: Canvas;
|
|
1705
|
+
color: CanvasText;
|
|
1706
|
+
max-width: calc(100vw - 2rem);
|
|
1707
|
+
max-height: calc(100svh - 2rem);
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
dialog:not([open]) {
|
|
1711
|
+
display: none;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
::backdrop {
|
|
1715
|
+
background: color-mix(in srgb, CanvasText 40%, transparent);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
[hidden] {
|
|
1719
|
+
display: none !important;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
::selection {
|
|
1723
|
+
background: Highlight;
|
|
1724
|
+
color: HighlightText;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1728
|
+
*,
|
|
1729
|
+
*::before,
|
|
1730
|
+
*::after {
|
|
1731
|
+
animation-duration: 0.01ms !important;
|
|
1732
|
+
animation-iteration-count: 1 !important;
|
|
1733
|
+
transition-duration: 0.01ms !important;
|
|
1734
|
+
scroll-behavior: auto !important;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
@media (pointer: coarse) {
|
|
1739
|
+
input,
|
|
1740
|
+
select,
|
|
1741
|
+
textarea {
|
|
1742
|
+
font-size: max(16px, 1em);
|
|
1743
|
+
}
|
|
1744
|
+
}`;
|
|
1745
|
+
|
|
1746
|
+
// ../core/dist/admin-client.js
|
|
1747
|
+
function isRecord2(value) {
|
|
1748
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1749
|
+
}
|
|
1750
|
+
function mountAdminBrand(doc, host, service, catalog) {
|
|
1751
|
+
const clip = (value, max) => {
|
|
1752
|
+
if (typeof value !== "string")
|
|
1753
|
+
return null;
|
|
1754
|
+
const trimmed = value.trim();
|
|
1755
|
+
if (trimmed === "")
|
|
1756
|
+
return null;
|
|
1757
|
+
return trimmed.length > max ? trimmed.slice(0, max) : trimmed;
|
|
1758
|
+
};
|
|
1759
|
+
const httpUrl = (value) => {
|
|
1760
|
+
if (typeof value !== "string" || value.length > 2e3)
|
|
1761
|
+
return null;
|
|
1762
|
+
try {
|
|
1763
|
+
const url = new URL(value);
|
|
1764
|
+
if (url.protocol !== "https:" && url.protocol !== "http:")
|
|
1765
|
+
return null;
|
|
1766
|
+
return url.href;
|
|
1767
|
+
} catch {
|
|
1768
|
+
return null;
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
const hexColor = (value) => {
|
|
1772
|
+
if (typeof value !== "string")
|
|
1773
|
+
return null;
|
|
1774
|
+
const raw = value.trim();
|
|
1775
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(raw) ? raw : null;
|
|
1776
|
+
};
|
|
1777
|
+
const hostname = (value) => {
|
|
1778
|
+
try {
|
|
1779
|
+
return new URL(value).hostname.replace(/^www\./, "");
|
|
1780
|
+
} catch {
|
|
1781
|
+
return value;
|
|
1782
|
+
}
|
|
1783
|
+
};
|
|
1784
|
+
host.replaceChildren();
|
|
1785
|
+
host.hidden = true;
|
|
1786
|
+
if (!isRecord2(catalog) || !Object.hasOwn(catalog, service))
|
|
1787
|
+
return;
|
|
1788
|
+
const row = catalog[service];
|
|
1789
|
+
if (!isRecord2(row))
|
|
1790
|
+
return;
|
|
1791
|
+
const vendor = clip(row.vendor, 80);
|
|
1792
|
+
const website = httpUrl(row.website);
|
|
1793
|
+
const guide = httpUrl(row.guide);
|
|
1794
|
+
const docs = httpUrl(row.docs);
|
|
1795
|
+
const logo = httpUrl(row.logo);
|
|
1796
|
+
const color = hexColor(row.color);
|
|
1797
|
+
const description = clip(row.description, 280);
|
|
1798
|
+
if (vendor === null || website === null && guide === null && docs === null)
|
|
1799
|
+
return;
|
|
1800
|
+
const add = (node) => {
|
|
1801
|
+
host.append(node);
|
|
1802
|
+
return node;
|
|
1803
|
+
};
|
|
1804
|
+
if (logo !== null) {
|
|
1805
|
+
const img = doc.createElement("img");
|
|
1806
|
+
img.alt = "";
|
|
1807
|
+
img.src = logo;
|
|
1808
|
+
img.width = 16;
|
|
1809
|
+
img.height = 16;
|
|
1810
|
+
img.decoding = "async";
|
|
1811
|
+
img.referrerPolicy = "no-referrer";
|
|
1812
|
+
if (color !== null)
|
|
1813
|
+
img.style.borderColor = color;
|
|
1814
|
+
add(img);
|
|
1815
|
+
}
|
|
1816
|
+
const name = doc.createElement("span");
|
|
1817
|
+
name.className = "vendor-name";
|
|
1818
|
+
name.textContent = vendor;
|
|
1819
|
+
if (description !== null)
|
|
1820
|
+
name.title = description;
|
|
1821
|
+
add(name);
|
|
1822
|
+
const sep = () => {
|
|
1823
|
+
const node = doc.createElement("span");
|
|
1824
|
+
node.className = "vendor-sep";
|
|
1825
|
+
node.setAttribute("aria-hidden", "true");
|
|
1826
|
+
node.textContent = "\xB7";
|
|
1827
|
+
return node;
|
|
1828
|
+
};
|
|
1829
|
+
const link = (href, label, title) => {
|
|
1830
|
+
const node = doc.createElement("a");
|
|
1831
|
+
node.className = "vendor-link";
|
|
1832
|
+
node.href = href;
|
|
1833
|
+
node.target = "_blank";
|
|
1834
|
+
node.rel = "noopener noreferrer";
|
|
1835
|
+
node.referrerPolicy = "no-referrer";
|
|
1836
|
+
node.textContent = label;
|
|
1837
|
+
node.title = title;
|
|
1838
|
+
return node;
|
|
1839
|
+
};
|
|
1840
|
+
if (website !== null) {
|
|
1841
|
+
add(sep());
|
|
1842
|
+
add(link(website, hostname(website), `${vendor} website`));
|
|
1843
|
+
}
|
|
1844
|
+
if (guide !== null) {
|
|
1845
|
+
add(sep());
|
|
1846
|
+
add(link(guide, "Docs", `Docs for ${vendor}`));
|
|
1847
|
+
}
|
|
1848
|
+
if (docs !== null) {
|
|
1849
|
+
add(sep());
|
|
1850
|
+
add(link(docs, "API", `${vendor} API reference`));
|
|
1851
|
+
}
|
|
1852
|
+
host.removeAttribute("hidden");
|
|
1853
|
+
host.hidden = false;
|
|
1854
|
+
}
|
|
1855
|
+
function startAdminBrand(env) {
|
|
1856
|
+
const requested = env.params.get("brands");
|
|
1857
|
+
let brandsUrl = env.brandsUrl;
|
|
1858
|
+
if (requested) {
|
|
1859
|
+
try {
|
|
1860
|
+
const url = new URL(requested, env.location.href);
|
|
1861
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "[::1]" || url.origin === env.location.origin;
|
|
1862
|
+
if ((url.protocol === "https:" || url.protocol === "http:") && local)
|
|
1863
|
+
brandsUrl = url.href;
|
|
1864
|
+
} catch {
|
|
1865
|
+
brandsUrl = env.brandsUrl;
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
const vendorHost = env.$("vendor");
|
|
1869
|
+
const brandRoot = env.document.querySelector("[data-mockingbird-admin]");
|
|
1870
|
+
const brandService = brandRoot?.getAttribute("data-service") || "";
|
|
1871
|
+
if (vendorHost === null)
|
|
1872
|
+
return;
|
|
1873
|
+
env.fetch(brandsUrl, {
|
|
1874
|
+
headers: { accept: "application/json" },
|
|
1875
|
+
signal: AbortSignal.timeout(5e3)
|
|
1876
|
+
}).then(async (response) => {
|
|
1877
|
+
if (!response.ok)
|
|
1878
|
+
return null;
|
|
1879
|
+
const catalog = await response.json();
|
|
1880
|
+
return catalog;
|
|
1881
|
+
}).then((catalog) => {
|
|
1882
|
+
mountAdminBrand(env.document, vendorHost, brandService, catalog);
|
|
1883
|
+
}).catch(() => {
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
function faultPresetList(value) {
|
|
1887
|
+
if (!isRecord2(value))
|
|
1888
|
+
throw new Error("Fault presets response was not an object");
|
|
1889
|
+
if (!Object.hasOwn(value, "presets") || value.presets === void 0 || value.presets === null) {
|
|
1890
|
+
return { presets: [] };
|
|
1891
|
+
}
|
|
1892
|
+
if (!Array.isArray(value.presets))
|
|
1893
|
+
throw new Error("Fault presets response was not a list");
|
|
1894
|
+
return {
|
|
1895
|
+
presets: value.presets.flatMap((item) => {
|
|
1896
|
+
if (!isRecord2(item) || typeof item.name !== "string")
|
|
1897
|
+
return [];
|
|
1898
|
+
return [{ name: item.name }];
|
|
1899
|
+
})
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
function bootAdmin(config) {
|
|
1903
|
+
const fieldKinds = ["string", "number", "boolean", "null", "object", "array", "unknown"];
|
|
1904
|
+
const sources = ["declared", "inferred", "mixed"];
|
|
1905
|
+
const isFieldKind = (value) => typeof value === "string" && fieldKinds.some((kind) => kind === value);
|
|
1906
|
+
const isSource = (value) => typeof value === "string" && sources.some((source) => source === value);
|
|
1907
|
+
const readJson2 = (text) => {
|
|
1908
|
+
const value = JSON.parse(text);
|
|
1909
|
+
return value;
|
|
1910
|
+
};
|
|
1911
|
+
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => {
|
|
1912
|
+
switch (char) {
|
|
1913
|
+
case "&":
|
|
1914
|
+
return "&";
|
|
1915
|
+
case "<":
|
|
1916
|
+
return "<";
|
|
1917
|
+
case ">":
|
|
1918
|
+
return ">";
|
|
1919
|
+
case '"':
|
|
1920
|
+
return """;
|
|
1921
|
+
default:
|
|
1922
|
+
return "'";
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
const el = (id, kind) => {
|
|
1926
|
+
const node = document.getElementById(id);
|
|
1927
|
+
if (!(node instanceof kind))
|
|
1928
|
+
throw new Error(`admin shell is missing #${id}`);
|
|
1929
|
+
return node;
|
|
1930
|
+
};
|
|
1931
|
+
const errorMessage = (data, fallback) => {
|
|
1932
|
+
if (!isRecord2(data))
|
|
1933
|
+
return fallback;
|
|
1934
|
+
const error = data.error;
|
|
1935
|
+
if (!isRecord2(error) || typeof error.message !== "string" || error.message === "") {
|
|
1936
|
+
return fallback;
|
|
1937
|
+
}
|
|
1938
|
+
return error.message;
|
|
1939
|
+
};
|
|
1940
|
+
const requireList = (value, label) => {
|
|
1941
|
+
if (value === void 0 || value === null)
|
|
1942
|
+
return [];
|
|
1943
|
+
if (!Array.isArray(value))
|
|
1944
|
+
throw new Error(`${label} was not a list`);
|
|
1945
|
+
return value;
|
|
1946
|
+
};
|
|
1947
|
+
const state = {
|
|
1948
|
+
namespace: "default",
|
|
1949
|
+
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1950
|
+
view: "overview",
|
|
1951
|
+
collection: "",
|
|
1952
|
+
shape: null,
|
|
1953
|
+
route: null,
|
|
1954
|
+
editing: null
|
|
1955
|
+
};
|
|
1956
|
+
const standard = new Set(config.standardRoutes);
|
|
1957
|
+
const banner = el("banner", HTMLElement);
|
|
1958
|
+
const keyInput = el("admin-key", HTMLInputElement);
|
|
1959
|
+
keyInput.value = state.key;
|
|
1960
|
+
const params = new URLSearchParams(location.search);
|
|
1961
|
+
const queryKey = params.get("key");
|
|
1962
|
+
if (queryKey) {
|
|
1963
|
+
state.key = queryKey;
|
|
1964
|
+
keyInput.value = state.key;
|
|
1965
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
1966
|
+
params.delete("key");
|
|
1967
|
+
const next = params.toString();
|
|
1968
|
+
history.replaceState(null, "", location.pathname + (next ? `?${next}` : ""));
|
|
1969
|
+
}
|
|
1970
|
+
const queryNamespace = params.get("namespace");
|
|
1971
|
+
if (queryNamespace)
|
|
1972
|
+
state.namespace = queryNamespace;
|
|
1973
|
+
const showError = (error) => {
|
|
1974
|
+
banner.textContent = error instanceof Error ? error.message : String(error);
|
|
1975
|
+
banner.classList.add("show");
|
|
1976
|
+
};
|
|
1977
|
+
const clearError = () => {
|
|
1978
|
+
banner.classList.remove("show");
|
|
1979
|
+
};
|
|
1980
|
+
const api = async (method, path, body) => {
|
|
1981
|
+
const headers = { accept: "application/json" };
|
|
1982
|
+
if (state.key !== "")
|
|
1983
|
+
headers[config.adminKeyHeader] = state.key;
|
|
1984
|
+
if (state.namespace !== "")
|
|
1985
|
+
headers["x-mockingbird-namespace"] = state.namespace;
|
|
1986
|
+
if (body !== void 0)
|
|
1987
|
+
headers["content-type"] = "application/json";
|
|
1988
|
+
const init = { method, headers };
|
|
1989
|
+
if (body !== void 0)
|
|
1990
|
+
init.body = JSON.stringify(body);
|
|
1991
|
+
const response = await fetch(config.adminPrefix + path, init);
|
|
1992
|
+
const text = await response.text();
|
|
1993
|
+
let data = null;
|
|
1994
|
+
if (text !== "") {
|
|
1995
|
+
try {
|
|
1996
|
+
data = readJson2(text);
|
|
1997
|
+
} catch {
|
|
1998
|
+
data = { raw: text };
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
if (response.status === 401) {
|
|
2002
|
+
keyInput.focus();
|
|
2003
|
+
throw new Error(errorMessage(data, "Admin key required"));
|
|
2004
|
+
}
|
|
2005
|
+
if (!response.ok) {
|
|
2006
|
+
throw new Error(errorMessage(data, `${response.status} ${method} ${path}`));
|
|
2007
|
+
}
|
|
2008
|
+
return data;
|
|
2009
|
+
};
|
|
2010
|
+
const originGet = async (path) => {
|
|
2011
|
+
const response = await fetch(path, { headers: { accept: "application/json" } });
|
|
2012
|
+
if (!response.ok)
|
|
2013
|
+
throw new Error(`${response.status} ${path}`);
|
|
2014
|
+
const value = await response.json();
|
|
2015
|
+
return value;
|
|
2016
|
+
};
|
|
2017
|
+
const panelApi = {
|
|
2018
|
+
get: (path) => api("GET", path),
|
|
2019
|
+
send: (method, path, body) => api(method, path, body),
|
|
2020
|
+
namespace: () => state.namespace
|
|
2021
|
+
};
|
|
2022
|
+
const isPanelScript = (value) => typeof value === "function";
|
|
2023
|
+
const setTheme = (theme) => {
|
|
2024
|
+
if (theme)
|
|
2025
|
+
document.documentElement.dataset.theme = theme;
|
|
2026
|
+
else
|
|
2027
|
+
delete document.documentElement.dataset.theme;
|
|
2028
|
+
};
|
|
2029
|
+
const storedTheme = localStorage.getItem("mockingbird-admin-theme");
|
|
2030
|
+
if (storedTheme)
|
|
2031
|
+
setTheme(storedTheme);
|
|
2032
|
+
el("theme", HTMLButtonElement).addEventListener("click", () => {
|
|
2033
|
+
const current = document.documentElement.dataset.theme;
|
|
2034
|
+
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
|
|
2035
|
+
const next = dark ? "light" : "dark";
|
|
2036
|
+
localStorage.setItem("mockingbird-admin-theme", next);
|
|
2037
|
+
setTheme(next);
|
|
2038
|
+
});
|
|
2039
|
+
const show = (view) => {
|
|
2040
|
+
state.view = view;
|
|
2041
|
+
for (const button of document.querySelectorAll("#nav button")) {
|
|
2042
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2043
|
+
continue;
|
|
2044
|
+
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false");
|
|
2045
|
+
}
|
|
2046
|
+
for (const section of document.querySelectorAll("main .view")) {
|
|
2047
|
+
if (!(section instanceof HTMLElement))
|
|
2048
|
+
continue;
|
|
2049
|
+
section.hidden = section.id !== `view-${view}`;
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
el("nav", HTMLElement).addEventListener("click", (event) => {
|
|
2053
|
+
const target = event.target;
|
|
2054
|
+
if (!(target instanceof Element))
|
|
2055
|
+
return;
|
|
2056
|
+
const button = target.closest("button[data-view]");
|
|
2057
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2058
|
+
return;
|
|
2059
|
+
const view = button.dataset.view;
|
|
2060
|
+
if (view === void 0)
|
|
2061
|
+
return;
|
|
2062
|
+
show(view);
|
|
2063
|
+
refresh().catch(showError);
|
|
2064
|
+
});
|
|
2065
|
+
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>`;
|
|
2066
|
+
const table = (columns, rows) => {
|
|
2067
|
+
if (rows.length === 0)
|
|
2068
|
+
return '<p class="empty">Nothing here yet.</p>';
|
|
2069
|
+
const head = `<tr>${columns.map((column) => `<th>${column.label}</th>`).join("")}</tr>`;
|
|
2070
|
+
const body = rows.map((row) => {
|
|
2071
|
+
const cells = columns.map((column) => `<td data-label="${column.label}">${column.cell(row)}</td>`).join("");
|
|
2072
|
+
const attrs = "attrs" in row && typeof row.attrs === "string" ? row.attrs : "";
|
|
2073
|
+
return `<tr${attrs}>${cells}</tr>`;
|
|
2074
|
+
}).join("");
|
|
2075
|
+
return `<table><thead>${head}</thead><tbody>${body}</tbody></table>`;
|
|
2076
|
+
};
|
|
2077
|
+
const preview = (value) => {
|
|
2078
|
+
const text = JSON.stringify(value) ?? "null";
|
|
2079
|
+
return esc(text.length > 180 ? `${text.slice(0, 180)}\u2026` : text);
|
|
2080
|
+
};
|
|
2081
|
+
const when = (epoch) => {
|
|
2082
|
+
const date = new Date(epoch);
|
|
2083
|
+
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString();
|
|
2084
|
+
};
|
|
2085
|
+
const clockCard = (epoch) => {
|
|
2086
|
+
const date = new Date(epoch);
|
|
2087
|
+
if (Number.isNaN(date.getTime()))
|
|
2088
|
+
return esc(String(epoch));
|
|
2089
|
+
const iso = date.toISOString();
|
|
2090
|
+
return `${esc(iso.slice(0, 10))}<span class="clock-time">${esc(iso.slice(11, 19))} UTC</span>`;
|
|
2091
|
+
};
|
|
2092
|
+
const decodeClock = (value) => {
|
|
2093
|
+
if (!isRecord2(value) || typeof value.now !== "number" || typeof value.offsetMs !== "number" || typeof value.frozen !== "boolean") {
|
|
2094
|
+
throw new Error("Clock response was not a clock");
|
|
2095
|
+
}
|
|
2096
|
+
return { now: value.now, offsetMs: value.offsetMs, frozen: value.frozen };
|
|
2097
|
+
};
|
|
2098
|
+
const decodeField = (value) => {
|
|
2099
|
+
if (!isRecord2(value) || typeof value.name !== "string" || !isFieldKind(value.kind)) {
|
|
2100
|
+
return void 0;
|
|
2101
|
+
}
|
|
2102
|
+
const field = {
|
|
2103
|
+
name: value.name,
|
|
2104
|
+
kind: value.kind,
|
|
2105
|
+
optional: value.optional === true
|
|
2106
|
+
};
|
|
2107
|
+
if (typeof value.description === "string")
|
|
2108
|
+
field.description = value.description;
|
|
2109
|
+
if (Array.isArray(value.fields)) {
|
|
2110
|
+
field.fields = value.fields.flatMap((item) => {
|
|
2111
|
+
const child = decodeField(item);
|
|
2112
|
+
return child === void 0 ? [] : [child];
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
return field;
|
|
2116
|
+
};
|
|
2117
|
+
const decodeCollection = (value) => {
|
|
2118
|
+
if (!isRecord2(value) || typeof value.name !== "string")
|
|
2119
|
+
return void 0;
|
|
2120
|
+
const fields = Array.isArray(value.fields) ? value.fields.flatMap((item) => {
|
|
2121
|
+
const field = decodeField(item);
|
|
2122
|
+
return field === void 0 ? [] : [field];
|
|
2123
|
+
}) : [];
|
|
2124
|
+
const collection = {
|
|
2125
|
+
name: value.name,
|
|
2126
|
+
label: typeof value.label === "string" ? value.label : value.name,
|
|
2127
|
+
count: typeof value.count === "number" ? value.count : 0,
|
|
2128
|
+
source: isSource(value.source) ? value.source : "inferred",
|
|
2129
|
+
fields
|
|
2130
|
+
};
|
|
2131
|
+
if (typeof value.description === "string")
|
|
2132
|
+
collection.description = value.description;
|
|
2133
|
+
return collection;
|
|
2134
|
+
};
|
|
2135
|
+
const decodeState = (value) => {
|
|
2136
|
+
if (!isRecord2(value) || !Array.isArray(value.collections)) {
|
|
2137
|
+
throw new Error("State response has no collection list");
|
|
2138
|
+
}
|
|
2139
|
+
return {
|
|
2140
|
+
namespace: typeof value.namespace === "string" ? value.namespace : state.namespace,
|
|
2141
|
+
storageNamespace: typeof value.storageNamespace === "string" ? value.storageNamespace : "",
|
|
2142
|
+
collections: value.collections.flatMap((item) => {
|
|
2143
|
+
const collection = decodeCollection(item);
|
|
2144
|
+
return collection === void 0 ? [] : [collection];
|
|
2145
|
+
})
|
|
2146
|
+
};
|
|
2147
|
+
};
|
|
2148
|
+
const decodeRecords = (value) => requireList(isRecord2(value) ? value.records : void 0, "Records").flatMap((item) => {
|
|
2149
|
+
if (!isRecord2(item) || typeof item.id !== "string")
|
|
2150
|
+
return [];
|
|
2151
|
+
return [
|
|
2152
|
+
{
|
|
2153
|
+
id: item.id,
|
|
2154
|
+
seq: typeof item.seq === "number" ? item.seq : 0,
|
|
2155
|
+
value: item.value,
|
|
2156
|
+
attrs: ` class="clickable" data-id="${esc(item.id)}"`
|
|
2157
|
+
}
|
|
2158
|
+
];
|
|
2159
|
+
});
|
|
2160
|
+
const textOf = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : "";
|
|
2161
|
+
const decodeJournal = (value) => requireList(isRecord2(value) ? value.requests : void 0, "Journal").flatMap((item) => {
|
|
2162
|
+
if (!isRecord2(item))
|
|
2163
|
+
return [];
|
|
2164
|
+
return [
|
|
2165
|
+
{
|
|
2166
|
+
at: textOf(item.at),
|
|
2167
|
+
method: textOf(item.method),
|
|
2168
|
+
path: textOf(item.path),
|
|
2169
|
+
status: textOf(item.status),
|
|
2170
|
+
durationMs: textOf(item.durationMs),
|
|
2171
|
+
operationId: textOf(item.operationId)
|
|
2172
|
+
}
|
|
2173
|
+
];
|
|
2174
|
+
});
|
|
2175
|
+
const decodeFaults = (value) => requireList(isRecord2(value) ? value.faults : void 0, "Faults").flatMap((item) => {
|
|
2176
|
+
if (!isRecord2(item))
|
|
2177
|
+
return [];
|
|
2178
|
+
return [
|
|
2179
|
+
{
|
|
2180
|
+
id: textOf(item.id),
|
|
2181
|
+
method: typeof item.method === "string" ? item.method : void 0,
|
|
2182
|
+
pathPrefix: typeof item.pathPrefix === "string" ? item.pathPrefix : void 0,
|
|
2183
|
+
operationId: typeof item.operationId === "string" ? item.operationId : void 0,
|
|
2184
|
+
status: typeof item.status === "number" ? item.status : void 0,
|
|
2185
|
+
effect: typeof item.effect === "string" ? item.effect : void 0,
|
|
2186
|
+
drop: typeof item.drop === "boolean" ? item.drop : void 0,
|
|
2187
|
+
latencyMs: typeof item.latencyMs === "number" ? item.latencyMs : void 0
|
|
2188
|
+
}
|
|
2189
|
+
];
|
|
2190
|
+
});
|
|
2191
|
+
const loadNamespaces = async () => {
|
|
2192
|
+
const data = await api("GET", "/namespaces");
|
|
2193
|
+
const select = el("namespace", HTMLSelectElement);
|
|
2194
|
+
const listed = requireList(isRecord2(data) ? data.namespaces : void 0, "Namespaces");
|
|
2195
|
+
const fallback = isRecord2(data) && typeof data.default === "string" && data.default !== "" ? data.default : "default";
|
|
2196
|
+
const names = (listed.length > 0 ? listed.map((name) => String(name)) : [fallback]).slice();
|
|
2197
|
+
if (!names.includes(state.namespace))
|
|
2198
|
+
names.unshift(state.namespace);
|
|
2199
|
+
select.innerHTML = names.map((name) => `<option value="${esc(name)}">${esc(name)}</option>`).join("");
|
|
2200
|
+
select.value = state.namespace;
|
|
2201
|
+
};
|
|
2202
|
+
const renderOverview = async () => {
|
|
2203
|
+
const [health, shape, clock, journal] = await Promise.all([
|
|
2204
|
+
originGet("/health"),
|
|
2205
|
+
api("GET", "/state"),
|
|
2206
|
+
api("GET", "/clock"),
|
|
2207
|
+
api("GET", "/requests?limit=8")
|
|
2208
|
+
]);
|
|
2209
|
+
if (!isRecord2(health))
|
|
2210
|
+
throw new Error("Health response was not an object");
|
|
2211
|
+
const decoded = decodeState(shape);
|
|
2212
|
+
state.shape = decoded;
|
|
2213
|
+
const time = decodeClock(clock);
|
|
2214
|
+
el("overview-cards", HTMLElement).innerHTML = cards([
|
|
2215
|
+
{
|
|
2216
|
+
k: "Service",
|
|
2217
|
+
v: esc(typeof health.service === "string" && health.service !== "" ? health.service : config.service)
|
|
2218
|
+
},
|
|
2219
|
+
{
|
|
2220
|
+
k: "Status",
|
|
2221
|
+
v: esc(typeof health.status === "string" && health.status !== "" ? health.status : "ok")
|
|
2222
|
+
},
|
|
2223
|
+
{ k: "Namespace", v: esc(state.namespace) },
|
|
2224
|
+
{ k: "Collections", v: String(decoded.collections.length) },
|
|
2225
|
+
{
|
|
2226
|
+
k: "Records",
|
|
2227
|
+
v: String(decoded.collections.reduce((sum, item) => sum + item.count, 0))
|
|
2228
|
+
},
|
|
2229
|
+
{ k: "Clock", v: clockCard(time.now) }
|
|
2230
|
+
]);
|
|
2231
|
+
el("overview-requests", HTMLElement).innerHTML = table([
|
|
2232
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2233
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2234
|
+
{
|
|
2235
|
+
label: "Status",
|
|
2236
|
+
cell: (row) => `<span class="status-${row.status.charAt(0)}">${esc(row.status)}</span>`
|
|
2237
|
+
},
|
|
2238
|
+
{ label: "Operation", cell: (row) => esc(row.operationId) }
|
|
2239
|
+
], decodeJournal(journal));
|
|
2240
|
+
};
|
|
2241
|
+
const schemaChips = (collection) => {
|
|
2242
|
+
if (collection.fields.length === 0) {
|
|
2243
|
+
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>';
|
|
2244
|
+
}
|
|
2245
|
+
return `<div class="chips">${collection.fields.map((field) => `<span class="chip">${esc(field.name)} <i>${esc(field.kind)}${field.optional ? "?" : ""}</i></span>`).join("")}</div>`;
|
|
2246
|
+
};
|
|
2247
|
+
const renderState = async () => {
|
|
2248
|
+
const shape = decodeState(await api("GET", "/state"));
|
|
2249
|
+
state.shape = shape;
|
|
2250
|
+
const host = el("collections", HTMLElement);
|
|
2251
|
+
if (shape.collections.length === 0) {
|
|
2252
|
+
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>';
|
|
2253
|
+
el("schema", HTMLElement).innerHTML = "";
|
|
2254
|
+
el("records", HTMLElement).innerHTML = "";
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2258
|
+
const first = shape.collections[0];
|
|
2259
|
+
if (first === void 0)
|
|
2260
|
+
return;
|
|
2261
|
+
state.collection = first.name;
|
|
2262
|
+
}
|
|
2263
|
+
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("");
|
|
2264
|
+
const selected = shape.collections.find((item) => item.name === state.collection);
|
|
2265
|
+
if (selected === void 0)
|
|
2266
|
+
return;
|
|
2267
|
+
const description = selected.description ? `<p class="lede">${esc(selected.description)}</p>` : "";
|
|
2268
|
+
el("schema", HTMLElement).innerHTML = `<h3>${esc(selected.label)}</h3>${description}${schemaChips(selected)}`;
|
|
2269
|
+
const page = await api("GET", `/state/${encodeURIComponent(state.collection)}?limit=50`);
|
|
2270
|
+
el("records", HTMLElement).innerHTML = table([
|
|
2271
|
+
{ label: "Id", cell: (row) => `<code>${esc(row.id)}</code>` },
|
|
2272
|
+
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2273
|
+
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2274
|
+
], decodeRecords(page));
|
|
2275
|
+
};
|
|
2276
|
+
const renderClock = async () => {
|
|
2277
|
+
const clock = decodeClock(await api("GET", "/clock"));
|
|
2278
|
+
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>`;
|
|
2279
|
+
const freeze = el("freeze", HTMLButtonElement);
|
|
2280
|
+
freeze.textContent = clock.frozen ? "Unfreeze" : "Freeze";
|
|
2281
|
+
freeze.dataset.frozen = clock.frozen ? "1" : "0";
|
|
2282
|
+
};
|
|
2283
|
+
const renderFaults = async () => {
|
|
2284
|
+
const [faults, presets] = await Promise.all([
|
|
2285
|
+
api("GET", "/faults"),
|
|
2286
|
+
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2287
|
+
]);
|
|
2288
|
+
const select = el("fault-preset", HTMLSelectElement);
|
|
2289
|
+
const current = select.value;
|
|
2290
|
+
const list = faultPresetList(presets).presets;
|
|
2291
|
+
select.innerHTML = `<option value="">Custom rule</option>${list.map((preset) => `<option value="${esc(preset.name)}">${esc(preset.name)}</option>`).join("")}`;
|
|
2292
|
+
select.value = current;
|
|
2293
|
+
el("fault-list", HTMLElement).innerHTML = table([
|
|
2294
|
+
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2295
|
+
{
|
|
2296
|
+
label: "Match",
|
|
2297
|
+
cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter((part) => Boolean(part)).join(" ") || "all")
|
|
2298
|
+
},
|
|
2299
|
+
{
|
|
2300
|
+
label: "Effect",
|
|
2301
|
+
cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || ""))
|
|
2302
|
+
}
|
|
2303
|
+
], decodeFaults(faults));
|
|
2304
|
+
};
|
|
2305
|
+
const renderJournal = async () => {
|
|
2306
|
+
const journal = await api("GET", "/requests?limit=100");
|
|
2307
|
+
el("journal", HTMLElement).innerHTML = table([
|
|
2308
|
+
{ label: "When", cell: (row) => esc(row.at) },
|
|
2309
|
+
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2310
|
+
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2311
|
+
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2312
|
+
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2313
|
+
], decodeJournal(journal));
|
|
2314
|
+
};
|
|
2315
|
+
const renderRoutes = async () => {
|
|
2316
|
+
const data = await api("GET", "/");
|
|
2317
|
+
if (!isRecord2(data))
|
|
2318
|
+
throw new Error("Route list was not an object");
|
|
2319
|
+
const routes = requireList(data.routes, "Routes").map((route) => String(route));
|
|
2320
|
+
el("route-list", HTMLElement).innerHTML = routes.map((route) => {
|
|
2321
|
+
const extra = standard.has(route) ? "" : " <span class='badge'>extension</span>";
|
|
2322
|
+
const parts = route.split(" ");
|
|
2323
|
+
const method = parts[0] ?? "";
|
|
2324
|
+
const path = parts.slice(1).join(" ");
|
|
2325
|
+
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>`;
|
|
2326
|
+
}).join("");
|
|
2327
|
+
};
|
|
2328
|
+
const renderers = {
|
|
2329
|
+
overview: renderOverview,
|
|
2330
|
+
state: renderState,
|
|
2331
|
+
clock: renderClock,
|
|
2332
|
+
faults: renderFaults,
|
|
2333
|
+
journal: renderJournal,
|
|
2334
|
+
routes: renderRoutes
|
|
2335
|
+
};
|
|
2336
|
+
async function refresh() {
|
|
2337
|
+
clearError();
|
|
2338
|
+
const render = renderers[state.view];
|
|
2339
|
+
if (render)
|
|
2340
|
+
await render();
|
|
2341
|
+
}
|
|
2342
|
+
el("namespace", HTMLSelectElement).addEventListener("change", () => {
|
|
2343
|
+
state.namespace = el("namespace", HTMLSelectElement).value;
|
|
2344
|
+
refresh().catch(showError);
|
|
2345
|
+
});
|
|
2346
|
+
keyInput.addEventListener("change", () => {
|
|
2347
|
+
state.key = keyInput.value.trim();
|
|
2348
|
+
sessionStorage.setItem("mockingbird-admin-key", state.key);
|
|
2349
|
+
refresh().catch(showError);
|
|
2350
|
+
});
|
|
2351
|
+
el("refresh", HTMLButtonElement).addEventListener("click", () => {
|
|
2352
|
+
refresh().catch(showError);
|
|
2353
|
+
});
|
|
2354
|
+
el("collections", HTMLElement).addEventListener("click", (event) => {
|
|
2355
|
+
const target = event.target;
|
|
2356
|
+
if (!(target instanceof Element))
|
|
2357
|
+
return;
|
|
2358
|
+
const button = target.closest("[data-collection]");
|
|
2359
|
+
if (!(button instanceof HTMLElement))
|
|
2360
|
+
return;
|
|
2361
|
+
const collection = button.dataset.collection;
|
|
2362
|
+
if (collection === void 0)
|
|
2363
|
+
return;
|
|
2364
|
+
state.collection = collection;
|
|
2365
|
+
renderState().catch(showError);
|
|
2366
|
+
});
|
|
2367
|
+
el("records", HTMLElement).addEventListener("click", (event) => {
|
|
2368
|
+
const target = event.target;
|
|
2369
|
+
if (!(target instanceof Element))
|
|
2370
|
+
return;
|
|
2371
|
+
const row = target.closest("[data-id]");
|
|
2372
|
+
if (!(row instanceof HTMLElement))
|
|
2373
|
+
return;
|
|
2374
|
+
const id = row.dataset.id;
|
|
2375
|
+
if (id === void 0)
|
|
2376
|
+
return;
|
|
2377
|
+
openEditor(id).catch(showError);
|
|
2378
|
+
});
|
|
2379
|
+
const editor = el("editor", HTMLDialogElement);
|
|
2380
|
+
const editorId = el("editor-id", HTMLInputElement);
|
|
2381
|
+
const editorValue = el("editor-value", HTMLTextAreaElement);
|
|
2382
|
+
const editorDelete = el("editor-delete", HTMLButtonElement);
|
|
2383
|
+
async function openEditor(id) {
|
|
2384
|
+
state.editing = id;
|
|
2385
|
+
el("editor-title", HTMLElement).textContent = id ? "Edit record" : "New record";
|
|
2386
|
+
editorId.value = id ?? "";
|
|
2387
|
+
editorId.readOnly = id !== null;
|
|
2388
|
+
editorDelete.hidden = id === null;
|
|
2389
|
+
if (id) {
|
|
2390
|
+
const record = await api("GET", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`);
|
|
2391
|
+
if (!isRecord2(record))
|
|
2392
|
+
throw new Error("Record response was not an object");
|
|
2393
|
+
editorValue.value = JSON.stringify(record.value, null, 2) ?? "";
|
|
2394
|
+
} else {
|
|
2395
|
+
editorValue.value = "{\n \n}";
|
|
2396
|
+
}
|
|
2397
|
+
editor.showModal();
|
|
2398
|
+
}
|
|
2399
|
+
el("new-record", HTMLButtonElement).addEventListener("click", () => {
|
|
2400
|
+
openEditor(null).catch(showError);
|
|
2401
|
+
});
|
|
2402
|
+
el("editor-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2403
|
+
const submitter = event.submitter;
|
|
2404
|
+
if (!(submitter instanceof HTMLButtonElement) || submitter.value !== "save")
|
|
2405
|
+
return;
|
|
2406
|
+
event.preventDefault();
|
|
2407
|
+
let value;
|
|
2408
|
+
try {
|
|
2409
|
+
value = readJson2(editorValue.value);
|
|
2410
|
+
} catch {
|
|
2411
|
+
showError(new Error("Value is not JSON"));
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
const id = editorId.value.trim();
|
|
2415
|
+
const path = `/state/${encodeURIComponent(state.collection)}`;
|
|
2416
|
+
const request = state.editing ? api("PUT", `${path}/${encodeURIComponent(id)}`, { value }) : api("POST", path, { id: id || void 0, value });
|
|
2417
|
+
request.then(() => {
|
|
2418
|
+
editor.close();
|
|
2419
|
+
return renderState();
|
|
2420
|
+
}).catch(showError);
|
|
2421
|
+
});
|
|
2422
|
+
editorDelete.addEventListener("click", () => {
|
|
2423
|
+
const id = state.editing;
|
|
2424
|
+
if (!id)
|
|
2425
|
+
return;
|
|
2426
|
+
api("DELETE", `/state/${encodeURIComponent(state.collection)}/${encodeURIComponent(id)}`).then(() => {
|
|
2427
|
+
editor.close();
|
|
2428
|
+
return renderState();
|
|
2429
|
+
}).catch(showError);
|
|
2430
|
+
});
|
|
2431
|
+
for (const button of document.querySelectorAll("[data-advance]")) {
|
|
2432
|
+
if (!(button instanceof HTMLButtonElement))
|
|
2433
|
+
continue;
|
|
2434
|
+
button.addEventListener("click", () => {
|
|
2435
|
+
const raw = button.dataset.advance;
|
|
2436
|
+
if (raw === void 0)
|
|
2437
|
+
return;
|
|
2438
|
+
api("POST", "/clock", { advance: Number(raw) }).then(() => renderClock()).catch(showError);
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
2441
|
+
el("freeze", HTMLButtonElement).addEventListener("click", () => {
|
|
2442
|
+
const frozen = el("freeze", HTMLButtonElement).dataset.frozen === "1";
|
|
2443
|
+
api("POST", "/clock", { freeze: !frozen }).then(() => renderClock()).catch(showError);
|
|
2444
|
+
});
|
|
2445
|
+
el("clock-reset", HTMLButtonElement).addEventListener("click", () => {
|
|
2446
|
+
api("POST", "/clock", { reset: true }).then(() => renderClock()).catch(showError);
|
|
2447
|
+
});
|
|
2448
|
+
el("clock-set", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2449
|
+
event.preventDefault();
|
|
2450
|
+
const raw = el("clock-instant", HTMLInputElement).value.trim();
|
|
2451
|
+
const set = /^-?\d+$/.test(raw) ? Number(raw) : raw;
|
|
2452
|
+
api("POST", "/clock", { set }).then(() => renderClock()).catch(showError);
|
|
2453
|
+
});
|
|
2454
|
+
el("fault-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2455
|
+
event.preventDefault();
|
|
2456
|
+
const preset = el("fault-preset", HTMLSelectElement).value;
|
|
2457
|
+
if (preset !== "") {
|
|
2458
|
+
api("POST", "/faults", { preset }).then(() => renderFaults()).catch(showError);
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
const status = Number(el("fault-status", HTMLInputElement).value);
|
|
2462
|
+
if (!Number.isFinite(status)) {
|
|
2463
|
+
showError(new Error("Status must be a number, or choose a preset"));
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const path = el("fault-path", HTMLInputElement).value;
|
|
2467
|
+
const method = el("fault-method", HTMLInputElement).value;
|
|
2468
|
+
api("POST", "/faults", {
|
|
2469
|
+
status,
|
|
2470
|
+
...path !== "" ? { pathPrefix: path } : {},
|
|
2471
|
+
...method !== "" ? { method } : {}
|
|
2472
|
+
}).then(() => renderFaults()).catch(showError);
|
|
2473
|
+
});
|
|
2474
|
+
el("fault-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2475
|
+
api("DELETE", "/faults").then(() => renderFaults()).catch(showError);
|
|
2476
|
+
});
|
|
2477
|
+
el("journal-clear", HTMLButtonElement).addEventListener("click", () => {
|
|
2478
|
+
api("DELETE", "/requests").then(() => renderJournal()).catch(showError);
|
|
2479
|
+
});
|
|
2480
|
+
el("route-list", HTMLElement).addEventListener("click", (event) => {
|
|
2481
|
+
const target = event.target;
|
|
2482
|
+
if (!(target instanceof Element))
|
|
2483
|
+
return;
|
|
2484
|
+
const button = target.closest("[data-route]");
|
|
2485
|
+
if (!(button instanceof HTMLElement))
|
|
2486
|
+
return;
|
|
2487
|
+
const route = button.dataset.route;
|
|
2488
|
+
if (route === void 0)
|
|
2489
|
+
return;
|
|
2490
|
+
state.route = route;
|
|
2491
|
+
el("route-title", HTMLElement).textContent = route;
|
|
2492
|
+
const method = route.split(" ")[0] ?? "";
|
|
2493
|
+
el("route-body", HTMLTextAreaElement).disabled = method === "GET" || method === "DELETE";
|
|
2494
|
+
});
|
|
2495
|
+
el("route-form", HTMLFormElement).addEventListener("submit", (event) => {
|
|
2496
|
+
event.preventDefault();
|
|
2497
|
+
const send = async () => {
|
|
2498
|
+
if (state.route === null)
|
|
2499
|
+
throw new Error("Choose a route first");
|
|
2500
|
+
const parts = state.route.split(" ");
|
|
2501
|
+
const method = parts[0];
|
|
2502
|
+
const path = parts.slice(1).join(" ");
|
|
2503
|
+
if (method === void 0)
|
|
2504
|
+
throw new Error("Choose a route first");
|
|
2505
|
+
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => prompt(`Value for ${key}`) || "");
|
|
2506
|
+
let body;
|
|
2507
|
+
const routeBody = el("route-body", HTMLTextAreaElement);
|
|
2508
|
+
if (method !== "GET" && method !== "DELETE" && routeBody.value.trim() !== "") {
|
|
2509
|
+
try {
|
|
2510
|
+
body = readJson2(routeBody.value);
|
|
2511
|
+
} catch {
|
|
2512
|
+
throw new Error("Body is not JSON");
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
const data = await api(method, concrete, body);
|
|
2516
|
+
el("route-result", HTMLElement).textContent = JSON.stringify(data, null, 2);
|
|
2517
|
+
};
|
|
2518
|
+
send().catch(showError);
|
|
2519
|
+
});
|
|
2520
|
+
const decodePanel = (value) => {
|
|
2521
|
+
if (!isRecord2(value) || typeof value.id !== "string" || typeof value.title !== "string") {
|
|
2522
|
+
return void 0;
|
|
2523
|
+
}
|
|
2524
|
+
const panel = {
|
|
2525
|
+
id: value.id,
|
|
2526
|
+
title: value.title,
|
|
2527
|
+
description: typeof value.description === "string" ? value.description : "",
|
|
2528
|
+
html: typeof value.html === "string" ? value.html : ""
|
|
2529
|
+
};
|
|
2530
|
+
if (typeof value.script === "string")
|
|
2531
|
+
panel.script = value.script;
|
|
2532
|
+
return panel;
|
|
2533
|
+
};
|
|
2534
|
+
const mountPanels = (panels) => {
|
|
2535
|
+
const nav = el("nav", HTMLElement);
|
|
2536
|
+
const views = el("views", HTMLElement);
|
|
2537
|
+
for (const panel of panels) {
|
|
2538
|
+
const button = document.createElement("button");
|
|
2539
|
+
button.type = "button";
|
|
2540
|
+
button.dataset.view = panel.id;
|
|
2541
|
+
button.textContent = panel.title;
|
|
2542
|
+
nav.append(button);
|
|
2543
|
+
const section = document.createElement("section");
|
|
2544
|
+
section.className = "view";
|
|
2545
|
+
section.id = `view-${panel.id}`;
|
|
2546
|
+
section.hidden = true;
|
|
2547
|
+
const title = document.createElement("h2");
|
|
2548
|
+
title.textContent = panel.title;
|
|
2549
|
+
const lede = document.createElement("p");
|
|
2550
|
+
lede.className = "lede";
|
|
2551
|
+
lede.textContent = panel.description;
|
|
2552
|
+
const body = document.createElement("div");
|
|
2553
|
+
body.className = "panel-body";
|
|
2554
|
+
body.id = `panel-${panel.id}`;
|
|
2555
|
+
body.innerHTML = panel.html;
|
|
2556
|
+
section.append(title, lede, body);
|
|
2557
|
+
views.append(section);
|
|
2558
|
+
if (panel.script === void 0)
|
|
2559
|
+
continue;
|
|
2560
|
+
try {
|
|
2561
|
+
const compiled = new Function("root", "api", panel.script);
|
|
2562
|
+
if (!isPanelScript(compiled))
|
|
2563
|
+
throw new Error("panel script did not compile");
|
|
2564
|
+
compiled(body, panelApi);
|
|
2565
|
+
} catch (error) {
|
|
2566
|
+
const note = document.createElement("p");
|
|
2567
|
+
note.className = "banner show";
|
|
2568
|
+
note.textContent = error instanceof Error ? error.message : String(error);
|
|
2569
|
+
body.append(note);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
};
|
|
2573
|
+
startAdminBrand({
|
|
2574
|
+
document,
|
|
2575
|
+
$: (id) => document.getElementById(id),
|
|
2576
|
+
params,
|
|
2577
|
+
location: { href: location.href, origin: location.origin },
|
|
2578
|
+
fetch,
|
|
2579
|
+
brandsUrl: config.brandsUrl
|
|
2580
|
+
});
|
|
2581
|
+
loadNamespaces().then(() => api("GET", "/ui/manifest")).then((manifest) => {
|
|
2582
|
+
const panels = requireList(isRecord2(manifest) ? manifest.panels : void 0, "Panels").flatMap((panel) => {
|
|
2583
|
+
const decoded = decodePanel(panel);
|
|
2584
|
+
return decoded === void 0 ? [] : [decoded];
|
|
2585
|
+
});
|
|
2586
|
+
mountPanels(panels);
|
|
2587
|
+
return refresh();
|
|
2588
|
+
}).catch(showError);
|
|
2589
|
+
}
|
|
2590
|
+
var clientPrelude = [isRecord2, mountAdminBrand, startAdminBrand, faultPresetList, bootAdmin].map((fn) => fn.toString()).join("\n");
|
|
2591
|
+
var adminClientSource = (config) => {
|
|
2592
|
+
const payload = JSON.stringify(config).replace(/</g, "\\u003c");
|
|
2593
|
+
return `<script>
|
|
2594
|
+
${clientPrelude}
|
|
2595
|
+
bootAdmin(${payload});
|
|
2596
|
+
</script>`;
|
|
2597
|
+
};
|
|
2598
|
+
|
|
1373
2599
|
// ../core/dist/surface.js
|
|
1374
2600
|
var defineMock = (runtime) => runtime;
|
|
1375
2601
|
var STANDARD_ADMIN_ROUTES = [
|
|
@@ -1438,94 +2664,106 @@ var escapeHtml = (value) => value.replace(/[&<>"']/g, (char) => {
|
|
|
1438
2664
|
return "'";
|
|
1439
2665
|
}
|
|
1440
2666
|
});
|
|
2667
|
+
var ADMIN_BRANDS_URL = "https://mockingbird.chrisvouga.dev/brands.json";
|
|
1441
2668
|
var renderAdminDocument = (service) => {
|
|
1442
2669
|
const name = escapeHtml(service);
|
|
1443
|
-
const
|
|
2670
|
+
const script = adminClientSource({
|
|
2671
|
+
standardRoutes: STANDARD_ADMIN_ROUTES,
|
|
2672
|
+
adminPrefix: ADMIN_PREFIX,
|
|
2673
|
+
adminKeyHeader: ADMIN_KEY_HEADER,
|
|
2674
|
+
brandsUrl: ADMIN_BRANDS_URL,
|
|
2675
|
+
service
|
|
2676
|
+
});
|
|
1444
2677
|
return `<!DOCTYPE html>
|
|
1445
2678
|
<html lang="en">
|
|
1446
2679
|
<head>
|
|
1447
2680
|
<meta charset="utf-8">
|
|
1448
2681
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1449
2682
|
<meta name="color-scheme" content="light dark">
|
|
1450
|
-
<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='
|
|
2683
|
+
<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">
|
|
1451
2684
|
<title>${name} admin</title>
|
|
1452
2685
|
<style>
|
|
2686
|
+
${CSS_RESET}
|
|
1453
2687
|
:root {
|
|
1454
2688
|
color-scheme: light;
|
|
1455
|
-
--bg: #
|
|
2689
|
+
--bg: #fafafa;
|
|
1456
2690
|
--bg-elev: #ffffff;
|
|
1457
|
-
--bg-muted: #
|
|
1458
|
-
--bg-inset: #
|
|
1459
|
-
--line: #
|
|
1460
|
-
--line-strong: #
|
|
1461
|
-
--ink: #
|
|
1462
|
-
--muted: #
|
|
1463
|
-
--faint: #
|
|
1464
|
-
--accent: #
|
|
1465
|
-
--accent-ink: #
|
|
1466
|
-
--accent-soft: #
|
|
1467
|
-
--ok: #
|
|
1468
|
-
--ok-soft: #
|
|
1469
|
-
--warn: #
|
|
1470
|
-
--warn-soft: #
|
|
1471
|
-
--err: #
|
|
1472
|
-
--err-soft: #
|
|
1473
|
-
--shadow:
|
|
1474
|
-
--sans: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
2691
|
+
--bg-muted: #f4f4f5;
|
|
2692
|
+
--bg-inset: #f4f4f5;
|
|
2693
|
+
--line: #dedee3;
|
|
2694
|
+
--line-strong: #c4c4cc;
|
|
2695
|
+
--ink: #18181b;
|
|
2696
|
+
--muted: #62626b;
|
|
2697
|
+
--faint: #71717a;
|
|
2698
|
+
--accent: #27272a;
|
|
2699
|
+
--accent-ink: #ffffff;
|
|
2700
|
+
--accent-soft: #f4f4f5;
|
|
2701
|
+
--ok: #166534;
|
|
2702
|
+
--ok-soft: #f0fdf4;
|
|
2703
|
+
--warn: #a16207;
|
|
2704
|
+
--warn-soft: #fefce8;
|
|
2705
|
+
--err: #a82d32;
|
|
2706
|
+
--err-soft: #fef2f2;
|
|
2707
|
+
--shadow: none;
|
|
2708
|
+
--sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
1475
2709
|
--mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
|
|
1476
|
-
--radius:
|
|
1477
|
-
--header:
|
|
1478
|
-
--nav:
|
|
2710
|
+
--radius: 10px;
|
|
2711
|
+
--header: 56px;
|
|
2712
|
+
--nav: 200px;
|
|
1479
2713
|
}
|
|
1480
2714
|
:root[data-theme="dark"] {
|
|
1481
2715
|
color-scheme: dark;
|
|
1482
|
-
--bg: #
|
|
1483
|
-
--bg-elev: #
|
|
1484
|
-
--bg-muted: #
|
|
1485
|
-
--bg-inset: #
|
|
1486
|
-
--line: #
|
|
1487
|
-
--line-strong: #
|
|
1488
|
-
--ink: #
|
|
1489
|
-
--muted: #
|
|
1490
|
-
--faint: #
|
|
1491
|
-
--accent: #
|
|
1492
|
-
--accent-ink: #
|
|
1493
|
-
--accent-soft: #
|
|
1494
|
-
--ok: #
|
|
1495
|
-
--ok-soft: #
|
|
1496
|
-
--warn: #
|
|
1497
|
-
--warn-soft: #
|
|
1498
|
-
--err: #
|
|
1499
|
-
--err-soft: #
|
|
1500
|
-
--shadow:
|
|
2716
|
+
--bg: #111113;
|
|
2717
|
+
--bg-elev: #19191c;
|
|
2718
|
+
--bg-muted: #242428;
|
|
2719
|
+
--bg-inset: #111113;
|
|
2720
|
+
--line: #36363c;
|
|
2721
|
+
--line-strong: #52525b;
|
|
2722
|
+
--ink: #f4f4f5;
|
|
2723
|
+
--muted: #a9a9b2;
|
|
2724
|
+
--faint: #71717a;
|
|
2725
|
+
--accent: #e4e4e7;
|
|
2726
|
+
--accent-ink: #18181b;
|
|
2727
|
+
--accent-soft: #242428;
|
|
2728
|
+
--ok: #86efac;
|
|
2729
|
+
--ok-soft: #14241b;
|
|
2730
|
+
--warn: #fde68a;
|
|
2731
|
+
--warn-soft: #2a2410;
|
|
2732
|
+
--err: #ffaaaa;
|
|
2733
|
+
--err-soft: #2c1516;
|
|
2734
|
+
--shadow: none;
|
|
1501
2735
|
}
|
|
1502
2736
|
@media (prefers-color-scheme: dark) {
|
|
1503
2737
|
:root:not([data-theme="light"]) {
|
|
1504
2738
|
color-scheme: dark;
|
|
1505
|
-
--bg: #
|
|
1506
|
-
--bg-elev: #
|
|
1507
|
-
--bg-muted: #
|
|
1508
|
-
--bg-inset: #
|
|
1509
|
-
--line: #
|
|
1510
|
-
--line-strong: #
|
|
1511
|
-
--ink: #
|
|
1512
|
-
--muted: #
|
|
1513
|
-
--faint: #
|
|
1514
|
-
--accent: #
|
|
1515
|
-
--accent-ink: #
|
|
1516
|
-
--accent-soft: #
|
|
1517
|
-
--ok: #
|
|
1518
|
-
--ok-soft: #
|
|
1519
|
-
--warn: #
|
|
1520
|
-
--warn-soft: #
|
|
1521
|
-
--err: #
|
|
1522
|
-
--err-soft: #
|
|
1523
|
-
--shadow:
|
|
2739
|
+
--bg: #111113;
|
|
2740
|
+
--bg-elev: #19191c;
|
|
2741
|
+
--bg-muted: #242428;
|
|
2742
|
+
--bg-inset: #111113;
|
|
2743
|
+
--line: #36363c;
|
|
2744
|
+
--line-strong: #52525b;
|
|
2745
|
+
--ink: #f4f4f5;
|
|
2746
|
+
--muted: #a9a9b2;
|
|
2747
|
+
--faint: #71717a;
|
|
2748
|
+
--accent: #e4e4e7;
|
|
2749
|
+
--accent-ink: #18181b;
|
|
2750
|
+
--accent-soft: #242428;
|
|
2751
|
+
--ok: #86efac;
|
|
2752
|
+
--ok-soft: #14241b;
|
|
2753
|
+
--warn: #fde68a;
|
|
2754
|
+
--warn-soft: #2a2410;
|
|
2755
|
+
--err: #ffaaaa;
|
|
2756
|
+
--err-soft: #2c1516;
|
|
2757
|
+
--shadow: none;
|
|
1524
2758
|
}
|
|
1525
2759
|
}
|
|
1526
|
-
|
|
1527
|
-
html
|
|
2760
|
+
/* Fill the viewport, then grow with the content so the background covers the scroll. */
|
|
2761
|
+
html { height: 100%; }
|
|
1528
2762
|
body {
|
|
2763
|
+
margin: 0;
|
|
2764
|
+
min-height: 100%;
|
|
2765
|
+
display: flex;
|
|
2766
|
+
flex-direction: column;
|
|
1529
2767
|
font-family: var(--sans);
|
|
1530
2768
|
background: var(--bg);
|
|
1531
2769
|
color: var(--ink);
|
|
@@ -1534,24 +2772,37 @@ var renderAdminDocument = (service) => {
|
|
|
1534
2772
|
padding-right: env(safe-area-inset-right);
|
|
1535
2773
|
-webkit-text-size-adjust: 100%;
|
|
1536
2774
|
}
|
|
1537
|
-
button, input, select, textarea { font: inherit; color: inherit; }
|
|
1538
|
-
button { cursor: pointer; }
|
|
1539
2775
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
1540
|
-
|
|
1541
|
-
* { scroll-behavior: auto !important; transition: none !important; }
|
|
1542
|
-
}
|
|
1543
|
-
.app { min-height: 100%; display: flex; flex-direction: column; }
|
|
2776
|
+
.app { flex: 1 0 auto; display: flex; flex-direction: column; min-width: 0; width: 100%; }
|
|
1544
2777
|
header.top {
|
|
1545
2778
|
position: sticky; top: 0; z-index: 5;
|
|
1546
2779
|
display: flex; align-items: center; gap: 12px;
|
|
1547
2780
|
min-height: var(--header); padding: 10px 16px;
|
|
1548
|
-
background:
|
|
2781
|
+
background: var(--bg-elev);
|
|
1549
2782
|
border-bottom: 1px solid var(--line);
|
|
1550
|
-
|
|
1551
|
-
}
|
|
1552
|
-
.brand {
|
|
1553
|
-
|
|
1554
|
-
|
|
2783
|
+
min-width: 0; max-width: 100%;
|
|
2784
|
+
}
|
|
2785
|
+
.brand {
|
|
2786
|
+
display: flex; flex-flow: row wrap; align-items: center; gap: 14px;
|
|
2787
|
+
min-width: 0; max-width: 100%; margin-right: auto;
|
|
2788
|
+
}
|
|
2789
|
+
.brand-id { display: flex; flex-direction: column; min-width: 0; flex: none; }
|
|
2790
|
+
.brand-id strong { font-size: 15px; letter-spacing: -0.01em; }
|
|
2791
|
+
.brand-id span { color: var(--faint); font-size: 12px; }
|
|
2792
|
+
.vendor {
|
|
2793
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
2794
|
+
min-width: 0; max-width: 100%; font-size: 12px; color: var(--muted);
|
|
2795
|
+
}
|
|
2796
|
+
.vendor[hidden] { display: none; }
|
|
2797
|
+
/* Logos are drawn for a light tile, same as the docs site, so the plate stays white. */
|
|
2798
|
+
.vendor img {
|
|
2799
|
+
width: 16px; height: 16px; padding: 2px; box-sizing: content-box;
|
|
2800
|
+
border-radius: 4px; border: 1px solid var(--line); background: #fff; object-fit: contain;
|
|
2801
|
+
}
|
|
2802
|
+
.vendor-name { color: var(--ink); font-weight: 600; }
|
|
2803
|
+
.vendor-sep { color: var(--faint); }
|
|
2804
|
+
.vendor-link { color: inherit; text-decoration: none; }
|
|
2805
|
+
.vendor-link:hover { color: var(--ink); text-decoration: underline; }
|
|
1555
2806
|
.controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
1556
2807
|
label.field { display: flex; flex-direction: column; gap: 2px; font-size: 11px; color: var(--faint); }
|
|
1557
2808
|
label.field input, label.field select, .controls input[type="password"] {
|
|
@@ -1569,7 +2820,7 @@ var renderAdminDocument = (service) => {
|
|
|
1569
2820
|
record or the full nav row would widen the page past the viewport. */
|
|
1570
2821
|
.layout {
|
|
1571
2822
|
display: grid; grid-template-columns: var(--nav) minmax(0, 1fr);
|
|
1572
|
-
flex: 1
|
|
2823
|
+
flex: 1 0 auto; min-width: 0;
|
|
1573
2824
|
}
|
|
1574
2825
|
nav.side {
|
|
1575
2826
|
border-right: 1px solid var(--line); padding: 12px;
|
|
@@ -1584,7 +2835,7 @@ var renderAdminDocument = (service) => {
|
|
|
1584
2835
|
main { padding: 20px; max-width: 1100px; width: 100%; min-width: 0; }
|
|
1585
2836
|
.view h2 { margin: 0 0 4px; font-size: 22px; letter-spacing: -0.02em; }
|
|
1586
2837
|
.lede { margin: 0 0 16px; color: var(--muted); }
|
|
1587
|
-
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
|
|
2838
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); gap: 12px; }
|
|
1588
2839
|
.card {
|
|
1589
2840
|
background: var(--bg-elev); border: 1px solid var(--line); border-radius: var(--radius);
|
|
1590
2841
|
padding: 14px 16px; box-shadow: var(--shadow);
|
|
@@ -1623,8 +2874,8 @@ var renderAdminDocument = (service) => {
|
|
|
1623
2874
|
.banner.show { display: block; }
|
|
1624
2875
|
.empty { color: var(--muted); padding: 24px 8px; }
|
|
1625
2876
|
dialog {
|
|
1626
|
-
border: 1px solid var(--line); border-radius:
|
|
1627
|
-
color: var(--ink); width: min(640px, calc(
|
|
2877
|
+
border: 1px solid var(--line); border-radius: var(--radius); padding: 0; background: var(--bg-elev);
|
|
2878
|
+
color: var(--ink); width: min(640px, calc(100% - 24px)); max-width: calc(100vw - 24px); box-shadow: none;
|
|
1628
2879
|
}
|
|
1629
2880
|
dialog::backdrop { background: rgb(20 19 17 / 0.45); }
|
|
1630
2881
|
dialog form, .dialog-body { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
|
|
@@ -1651,7 +2902,7 @@ var renderAdminDocument = (service) => {
|
|
|
1651
2902
|
padding-top: max(10px, env(safe-area-inset-top));
|
|
1652
2903
|
}
|
|
1653
2904
|
input, select, textarea { font-size: 16px; }
|
|
1654
|
-
.controls { width: 100%; }
|
|
2905
|
+
.brand, .vendor, .controls { width: 100%; }
|
|
1655
2906
|
label.field, label.field select, label.field input { flex: 1; min-width: 0; }
|
|
1656
2907
|
.layout { grid-template-columns: minmax(0, 1fr); }
|
|
1657
2908
|
nav.side {
|
|
@@ -1682,8 +2933,11 @@ var renderAdminDocument = (service) => {
|
|
|
1682
2933
|
<div class="app" data-service="${name}" data-mockingbird-admin>
|
|
1683
2934
|
<header class="top">
|
|
1684
2935
|
<div class="brand">
|
|
1685
|
-
<
|
|
1686
|
-
|
|
2936
|
+
<div class="brand-id">
|
|
2937
|
+
<strong>${name}</strong>
|
|
2938
|
+
<span>Admin</span>
|
|
2939
|
+
</div>
|
|
2940
|
+
<div class="vendor" id="vendor" hidden></div>
|
|
1687
2941
|
</div>
|
|
1688
2942
|
<div class="controls">
|
|
1689
2943
|
<label class="field">Namespace
|
|
@@ -1813,460 +3067,7 @@ var renderAdminDocument = (service) => {
|
|
|
1813
3067
|
</div>
|
|
1814
3068
|
</form>
|
|
1815
3069
|
</dialog>
|
|
1816
|
-
|
|
1817
|
-
(() => {
|
|
1818
|
-
const STANDARD = new Set(${standard})
|
|
1819
|
-
const state = {
|
|
1820
|
-
namespace: "default",
|
|
1821
|
-
key: sessionStorage.getItem("mockingbird-admin-key") || "",
|
|
1822
|
-
view: "overview",
|
|
1823
|
-
collection: "",
|
|
1824
|
-
shape: null,
|
|
1825
|
-
route: null,
|
|
1826
|
-
editing: null
|
|
1827
|
-
}
|
|
1828
|
-
const $ = (id) => document.getElementById(id)
|
|
1829
|
-
const banner = $("banner")
|
|
1830
|
-
const keyInput = $("admin-key")
|
|
1831
|
-
keyInput.value = state.key
|
|
1832
|
-
const params = new URLSearchParams(location.search)
|
|
1833
|
-
if (params.get("key")) {
|
|
1834
|
-
state.key = params.get("key")
|
|
1835
|
-
keyInput.value = state.key
|
|
1836
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
1837
|
-
params.delete("key")
|
|
1838
|
-
const next = params.toString()
|
|
1839
|
-
history.replaceState(null, "", location.pathname + (next ? "?" + next : ""))
|
|
1840
|
-
}
|
|
1841
|
-
if (params.get("namespace")) state.namespace = params.get("namespace")
|
|
1842
|
-
|
|
1843
|
-
const showError = (error) => {
|
|
1844
|
-
banner.textContent = error instanceof Error ? error.message : String(error)
|
|
1845
|
-
banner.classList.add("show")
|
|
1846
|
-
}
|
|
1847
|
-
const clearError = () => banner.classList.remove("show")
|
|
1848
|
-
|
|
1849
|
-
async function originGet(path) {
|
|
1850
|
-
const response = await fetch(path, { headers: { accept: "application/json" } })
|
|
1851
|
-
if (!response.ok) throw new Error(response.status + " " + path)
|
|
1852
|
-
return response.json()
|
|
1853
|
-
}
|
|
1854
|
-
async function api(method, path, body) {
|
|
1855
|
-
const headers = { accept: "application/json" }
|
|
1856
|
-
if (state.key) headers["${ADMIN_KEY_HEADER}"] = state.key
|
|
1857
|
-
if (state.namespace) headers["x-mockingbird-namespace"] = state.namespace
|
|
1858
|
-
if (body !== undefined) headers["content-type"] = "application/json"
|
|
1859
|
-
const response = await fetch("${ADMIN_PREFIX}" + path, {
|
|
1860
|
-
method,
|
|
1861
|
-
headers,
|
|
1862
|
-
body: body === undefined ? undefined : JSON.stringify(body)
|
|
1863
|
-
})
|
|
1864
|
-
const text = await response.text()
|
|
1865
|
-
let data = null
|
|
1866
|
-
if (text) {
|
|
1867
|
-
try { data = JSON.parse(text) } catch { data = { raw: text } }
|
|
1868
|
-
}
|
|
1869
|
-
if (response.status === 401) {
|
|
1870
|
-
keyInput.focus()
|
|
1871
|
-
throw new Error((data && data.error && data.error.message) || "Admin key required")
|
|
1872
|
-
}
|
|
1873
|
-
if (!response.ok) {
|
|
1874
|
-
throw new Error((data && data.error && data.error.message) || (response.status + " " + method + " " + path))
|
|
1875
|
-
}
|
|
1876
|
-
return data
|
|
1877
|
-
}
|
|
1878
|
-
const panelApi = {
|
|
1879
|
-
get: (path) => api("GET", path),
|
|
1880
|
-
send: (method, path, body) => api(method, path, body),
|
|
1881
|
-
namespace: () => state.namespace
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
function setTheme(theme) {
|
|
1885
|
-
if (theme) document.documentElement.dataset.theme = theme
|
|
1886
|
-
else delete document.documentElement.dataset.theme
|
|
1887
|
-
}
|
|
1888
|
-
const storedTheme = localStorage.getItem("mockingbird-admin-theme")
|
|
1889
|
-
if (storedTheme) setTheme(storedTheme)
|
|
1890
|
-
$("theme").addEventListener("click", () => {
|
|
1891
|
-
const current = document.documentElement.dataset.theme
|
|
1892
|
-
const dark = current ? current === "dark" : matchMedia("(prefers-color-scheme: dark)").matches
|
|
1893
|
-
const next = dark ? "light" : "dark"
|
|
1894
|
-
localStorage.setItem("mockingbird-admin-theme", next)
|
|
1895
|
-
setTheme(next)
|
|
1896
|
-
})
|
|
1897
|
-
|
|
1898
|
-
function show(view) {
|
|
1899
|
-
state.view = view
|
|
1900
|
-
for (const button of document.querySelectorAll("#nav button")) {
|
|
1901
|
-
button.setAttribute("aria-current", button.dataset.view === view ? "true" : "false")
|
|
1902
|
-
}
|
|
1903
|
-
for (const section of document.querySelectorAll("main .view")) {
|
|
1904
|
-
section.hidden = section.id !== "view-" + view
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
document.getElementById("nav").addEventListener("click", (event) => {
|
|
1908
|
-
const button = event.target.closest("button[data-view]")
|
|
1909
|
-
if (!button) return
|
|
1910
|
-
show(button.dataset.view)
|
|
1911
|
-
refresh().catch(showError)
|
|
1912
|
-
})
|
|
1913
|
-
|
|
1914
|
-
function cards(items) {
|
|
1915
|
-
return '<div class="grid">' + items.map((item) =>
|
|
1916
|
-
'<article class="card"><div class="k">' + item.k + '</div><div class="v">' + item.v + '</div></article>'
|
|
1917
|
-
).join("") + "</div>"
|
|
1918
|
-
}
|
|
1919
|
-
function table(columns, rows) {
|
|
1920
|
-
if (rows.length === 0) return '<p class="empty">Nothing here yet.</p>'
|
|
1921
|
-
const head = "<tr>" + columns.map((column) => "<th>" + column.label + "</th>").join("") + "</tr>"
|
|
1922
|
-
const body = rows.map((row) => "<tr" + (row.attrs || "") + ">" + columns.map((column) =>
|
|
1923
|
-
'<td data-label="' + column.label + '">' + column.cell(row) + "</td>"
|
|
1924
|
-
).join("") + "</tr>").join("")
|
|
1925
|
-
return "<table><thead>" + head + "</thead><tbody>" + body + "</tbody></table>"
|
|
1926
|
-
}
|
|
1927
|
-
const esc = (value) => String(value ?? "").replace(/[&<>"']/g, (char) => ({
|
|
1928
|
-
"&": "&", "<": "<", ">": ">", '"': """, "'": "'"
|
|
1929
|
-
}[char]))
|
|
1930
|
-
const preview = (value) => {
|
|
1931
|
-
const text = JSON.stringify(value)
|
|
1932
|
-
return esc(text.length > 180 ? text.slice(0, 180) + "\u2026" : text)
|
|
1933
|
-
}
|
|
1934
|
-
const when = (epoch) => {
|
|
1935
|
-
const date = new Date(epoch)
|
|
1936
|
-
return Number.isNaN(date.getTime()) ? String(epoch) : date.toISOString()
|
|
1937
|
-
}
|
|
1938
|
-
const clockCard = (epoch) => {
|
|
1939
|
-
const date = new Date(epoch)
|
|
1940
|
-
if (Number.isNaN(date.getTime())) return esc(String(epoch))
|
|
1941
|
-
const iso = date.toISOString()
|
|
1942
|
-
return esc(iso.slice(0, 10)) + '<span class="clock-time">' + esc(iso.slice(11, 19)) + " UTC</span>"
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
async function loadNamespaces() {
|
|
1946
|
-
const data = await api("GET", "/namespaces")
|
|
1947
|
-
const select = $("namespace")
|
|
1948
|
-
const names = data.namespaces && data.namespaces.length ? data.namespaces : [data.default || "default"]
|
|
1949
|
-
if (!names.includes(state.namespace)) names.unshift(state.namespace)
|
|
1950
|
-
select.innerHTML = names.map((name) =>
|
|
1951
|
-
'<option value="' + esc(name) + '">' + esc(name) + "</option>"
|
|
1952
|
-
).join("")
|
|
1953
|
-
select.value = state.namespace
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
async function renderOverview() {
|
|
1957
|
-
const [health, shape, clock, journal] = await Promise.all([
|
|
1958
|
-
originGet("/health"),
|
|
1959
|
-
api("GET", "/state"),
|
|
1960
|
-
api("GET", "/clock"),
|
|
1961
|
-
api("GET", "/requests?limit=8")
|
|
1962
|
-
])
|
|
1963
|
-
state.shape = shape
|
|
1964
|
-
$("overview-cards").innerHTML = cards([
|
|
1965
|
-
{ k: "Service", v: esc(health.service || "${name}") },
|
|
1966
|
-
{ k: "Status", v: esc(health.status || "ok") },
|
|
1967
|
-
{ k: "Namespace", v: esc(state.namespace) },
|
|
1968
|
-
{ k: "Collections", v: String(shape.collections.length) },
|
|
1969
|
-
{ k: "Records", v: String(shape.collections.reduce((sum, item) => sum + item.count, 0)) },
|
|
1970
|
-
{ k: "Clock", v: clockCard(clock.now) }
|
|
1971
|
-
])
|
|
1972
|
-
const requests = (journal && journal.requests) || []
|
|
1973
|
-
$("overview-requests").innerHTML = table(
|
|
1974
|
-
[
|
|
1975
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
1976
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
1977
|
-
{ label: "Status", cell: (row) => '<span class="status-' + String(row.status).charAt(0) + '">' + esc(row.status) + "</span>" },
|
|
1978
|
-
{ label: "Operation", cell: (row) => esc(row.operationId || "") }
|
|
1979
|
-
],
|
|
1980
|
-
requests
|
|
1981
|
-
)
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
function schemaChips(collection) {
|
|
1985
|
-
if (!collection || collection.fields.length === 0) {
|
|
1986
|
-
return '<p class="empty">No fields yet. Add a record or declare the collection on the service.</p>'
|
|
1987
|
-
}
|
|
1988
|
-
return '<div class="chips">' + collection.fields.map((field) =>
|
|
1989
|
-
'<span class="chip">' + esc(field.name) + ' <i>' + esc(field.kind) + (field.optional ? "?" : "") + "</i></span>"
|
|
1990
|
-
).join("") + "</div>"
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
|
-
async function renderState() {
|
|
1994
|
-
const shape = await api("GET", "/state")
|
|
1995
|
-
state.shape = shape
|
|
1996
|
-
const host = $("collections")
|
|
1997
|
-
if (shape.collections.length === 0) {
|
|
1998
|
-
host.innerHTML = '<p class="empty">This namespace has no collections yet.</p>'
|
|
1999
|
-
$("schema").innerHTML = ""
|
|
2000
|
-
$("records").innerHTML = ""
|
|
2001
|
-
return
|
|
2002
|
-
}
|
|
2003
|
-
if (!shape.collections.some((item) => item.name === state.collection)) {
|
|
2004
|
-
state.collection = shape.collections[0].name
|
|
2005
|
-
}
|
|
2006
|
-
host.innerHTML = shape.collections.map((item) =>
|
|
2007
|
-
'<button type="button" class="collection" data-collection="' + esc(item.name) + '" aria-current="' +
|
|
2008
|
-
(item.name === state.collection ? "true" : "false") + '"><strong>' + esc(item.label) +
|
|
2009
|
-
'</strong><small>' + esc(item.count) + " \xB7 " + esc(item.source) + "</small></button>"
|
|
2010
|
-
).join("")
|
|
2011
|
-
const selected = shape.collections.find((item) => item.name === state.collection)
|
|
2012
|
-
$("schema").innerHTML = "<h3>" + esc(selected.label) + "</h3>" +
|
|
2013
|
-
(selected.description ? "<p class=" + '"lede">' + esc(selected.description) + "</p>" : "") +
|
|
2014
|
-
schemaChips(selected)
|
|
2015
|
-
const page = await api("GET", "/state/" + encodeURIComponent(state.collection) + "?limit=50")
|
|
2016
|
-
const rows = page.records.map((record) => ({
|
|
2017
|
-
...record,
|
|
2018
|
-
attrs: ' class="clickable" data-id="' + esc(record.id) + '"'
|
|
2019
|
-
}))
|
|
2020
|
-
$("records").innerHTML = table(
|
|
2021
|
-
[
|
|
2022
|
-
{ label: "Id", cell: (row) => "<code>" + esc(row.id) + "</code>" },
|
|
2023
|
-
{ label: "Seq", cell: (row) => esc(row.seq) },
|
|
2024
|
-
{ label: "Value", cell: (row) => preview(row.value) }
|
|
2025
|
-
],
|
|
2026
|
-
rows
|
|
2027
|
-
)
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
async function renderClock() {
|
|
2031
|
-
const clock = await api("GET", "/clock")
|
|
2032
|
-
$("clock-readout").innerHTML = "<div class='k'>Now</div><div class='v'>" + esc(when(clock.now)) +
|
|
2033
|
-
"</div><p>" + esc(clock.now) + " ms \xB7 offset " + esc(clock.offsetMs) + " ms " +
|
|
2034
|
-
(clock.frozen ? "<span class='badge warn'>frozen</span>" : "<span class='badge ok'>live</span>") + "</p>"
|
|
2035
|
-
$("freeze").textContent = clock.frozen ? "Unfreeze" : "Freeze"
|
|
2036
|
-
$("freeze").dataset.frozen = clock.frozen ? "1" : "0"
|
|
2037
|
-
}
|
|
2038
|
-
|
|
2039
|
-
async function renderFaults() {
|
|
2040
|
-
const [faults, presets] = await Promise.all([
|
|
2041
|
-
api("GET", "/faults"),
|
|
2042
|
-
api("GET", "/faults/presets").catch(() => ({ presets: [] }))
|
|
2043
|
-
])
|
|
2044
|
-
const select = $("fault-preset")
|
|
2045
|
-
const current = select.value
|
|
2046
|
-
const list = presets.presets || []
|
|
2047
|
-
select.innerHTML = '<option value="">Custom rule</option>' + list.map((preset) =>
|
|
2048
|
-
'<option value="' + esc(preset.name) + '">' + esc(preset.name) + "</option>"
|
|
2049
|
-
).join("")
|
|
2050
|
-
select.value = current
|
|
2051
|
-
const rules = faults.faults || []
|
|
2052
|
-
$("fault-list").innerHTML = table(
|
|
2053
|
-
[
|
|
2054
|
-
{ label: "Id", cell: (row) => esc(row.id) },
|
|
2055
|
-
{ label: "Match", cell: (row) => esc([row.method, row.pathPrefix, row.operationId].filter(Boolean).join(" ") || "all") },
|
|
2056
|
-
{ label: "Effect", cell: (row) => esc(row.status ?? row.effect ?? (row.drop ? "drop" : row.latencyMs || "")) }
|
|
2057
|
-
],
|
|
2058
|
-
rules
|
|
2059
|
-
)
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
async function renderJournal() {
|
|
2063
|
-
const journal = await api("GET", "/requests?limit=100")
|
|
2064
|
-
$("journal").innerHTML = table(
|
|
2065
|
-
[
|
|
2066
|
-
{ label: "When", cell: (row) => esc(row.at || "") },
|
|
2067
|
-
{ label: "Method", cell: (row) => esc(row.method) },
|
|
2068
|
-
{ label: "Path", cell: (row) => esc(row.path) },
|
|
2069
|
-
{ label: "Status", cell: (row) => esc(row.status) },
|
|
2070
|
-
{ label: "ms", cell: (row) => esc(row.durationMs) }
|
|
2071
|
-
],
|
|
2072
|
-
journal.requests || []
|
|
2073
|
-
)
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
async function renderRoutes() {
|
|
2077
|
-
const data = await api("GET", "/")
|
|
2078
|
-
const routes = data.routes || []
|
|
2079
|
-
$("route-list").innerHTML = routes.map((route) => {
|
|
2080
|
-
const extra = STANDARD.has(route) ? "" : " <span class='badge'>extension</span>"
|
|
2081
|
-
return '<div class="route"><span class="method">' + esc(route.split(" ")[0]) +
|
|
2082
|
-
'</span><span>' + esc(route.split(" ").slice(1).join(" ")) + extra +
|
|
2083
|
-
'</span><button class="btn" type="button" data-route="' + esc(route) + '">Use</button></div>'
|
|
2084
|
-
}).join("")
|
|
2085
|
-
}
|
|
2086
|
-
|
|
2087
|
-
const renderers = {
|
|
2088
|
-
overview: renderOverview,
|
|
2089
|
-
state: renderState,
|
|
2090
|
-
clock: renderClock,
|
|
2091
|
-
faults: renderFaults,
|
|
2092
|
-
journal: renderJournal,
|
|
2093
|
-
routes: renderRoutes
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
async function refresh() {
|
|
2097
|
-
clearError()
|
|
2098
|
-
if (state.view in renderers) await renderers[state.view]()
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
$("namespace").addEventListener("change", () => {
|
|
2102
|
-
state.namespace = $("namespace").value
|
|
2103
|
-
refresh().catch(showError)
|
|
2104
|
-
})
|
|
2105
|
-
keyInput.addEventListener("change", () => {
|
|
2106
|
-
state.key = keyInput.value.trim()
|
|
2107
|
-
sessionStorage.setItem("mockingbird-admin-key", state.key)
|
|
2108
|
-
refresh().catch(showError)
|
|
2109
|
-
})
|
|
2110
|
-
$("refresh").addEventListener("click", () => refresh().catch(showError))
|
|
2111
|
-
|
|
2112
|
-
$("collections").addEventListener("click", (event) => {
|
|
2113
|
-
const button = event.target.closest("[data-collection]")
|
|
2114
|
-
if (!button) return
|
|
2115
|
-
state.collection = button.dataset.collection
|
|
2116
|
-
renderState().catch(showError)
|
|
2117
|
-
})
|
|
2118
|
-
$("records").addEventListener("click", (event) => {
|
|
2119
|
-
const row = event.target.closest("[data-id]")
|
|
2120
|
-
if (!row) return
|
|
2121
|
-
openEditor(row.dataset.id).catch(showError)
|
|
2122
|
-
})
|
|
2123
|
-
|
|
2124
|
-
const editor = $("editor")
|
|
2125
|
-
async function openEditor(id) {
|
|
2126
|
-
state.editing = id || null
|
|
2127
|
-
$("editor-title").textContent = id ? "Edit record" : "New record"
|
|
2128
|
-
$("editor-id").value = id || ""
|
|
2129
|
-
$("editor-id").readOnly = Boolean(id)
|
|
2130
|
-
$("editor-delete").hidden = !id
|
|
2131
|
-
if (id) {
|
|
2132
|
-
const record = await api("GET", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
2133
|
-
$("editor-value").value = JSON.stringify(record.value, null, 2)
|
|
2134
|
-
} else {
|
|
2135
|
-
$("editor-value").value = "{\\n \\n}"
|
|
2136
|
-
}
|
|
2137
|
-
editor.showModal()
|
|
2138
|
-
}
|
|
2139
|
-
$("new-record").addEventListener("click", () => openEditor(null).catch(showError))
|
|
2140
|
-
$("editor-form").addEventListener("submit", (event) => {
|
|
2141
|
-
const submitter = event.submitter
|
|
2142
|
-
if (!submitter || submitter.value !== "save") return
|
|
2143
|
-
event.preventDefault()
|
|
2144
|
-
let value
|
|
2145
|
-
try { value = JSON.parse($("editor-value").value) }
|
|
2146
|
-
catch { showError(new Error("Value is not JSON")); return }
|
|
2147
|
-
const id = $("editor-id").value.trim()
|
|
2148
|
-
const path = "/state/" + encodeURIComponent(state.collection)
|
|
2149
|
-
const request = state.editing
|
|
2150
|
-
? api("PUT", path + "/" + encodeURIComponent(id), { value })
|
|
2151
|
-
: api("POST", path, { id: id || undefined, value })
|
|
2152
|
-
request.then(() => { editor.close(); return renderState() }).catch(showError)
|
|
2153
|
-
})
|
|
2154
|
-
$("editor-delete").addEventListener("click", () => {
|
|
2155
|
-
const id = state.editing
|
|
2156
|
-
if (!id) return
|
|
2157
|
-
api("DELETE", "/state/" + encodeURIComponent(state.collection) + "/" + encodeURIComponent(id))
|
|
2158
|
-
.then(() => { editor.close(); return renderState() })
|
|
2159
|
-
.catch(showError)
|
|
2160
|
-
})
|
|
2161
|
-
|
|
2162
|
-
document.querySelectorAll("[data-advance]").forEach((button) => {
|
|
2163
|
-
button.addEventListener("click", () => {
|
|
2164
|
-
api("POST", "/clock", { advance: Number(button.dataset.advance) }).then(renderClock).catch(showError)
|
|
2165
|
-
})
|
|
2166
|
-
})
|
|
2167
|
-
$("freeze").addEventListener("click", () => {
|
|
2168
|
-
const frozen = $("freeze").dataset.frozen === "1"
|
|
2169
|
-
api("POST", "/clock", { freeze: !frozen }).then(renderClock).catch(showError)
|
|
2170
|
-
})
|
|
2171
|
-
$("clock-reset").addEventListener("click", () => {
|
|
2172
|
-
api("POST", "/clock", { reset: true }).then(renderClock).catch(showError)
|
|
2173
|
-
})
|
|
2174
|
-
$("clock-set").addEventListener("submit", (event) => {
|
|
2175
|
-
event.preventDefault()
|
|
2176
|
-
const raw = $("clock-instant").value.trim()
|
|
2177
|
-
const set = /^-?\\d+$/.test(raw) ? Number(raw) : raw
|
|
2178
|
-
api("POST", "/clock", { set }).then(renderClock).catch(showError)
|
|
2179
|
-
})
|
|
2180
|
-
|
|
2181
|
-
$("fault-form").addEventListener("submit", (event) => {
|
|
2182
|
-
event.preventDefault()
|
|
2183
|
-
const preset = $("fault-preset").value
|
|
2184
|
-
const body = preset
|
|
2185
|
-
? { preset }
|
|
2186
|
-
: {
|
|
2187
|
-
status: Number($("fault-status").value),
|
|
2188
|
-
...($("fault-path").value ? { pathPrefix: $("fault-path").value } : {}),
|
|
2189
|
-
...($("fault-method").value ? { method: $("fault-method").value } : {})
|
|
2190
|
-
}
|
|
2191
|
-
if (!preset && !Number.isFinite(body.status)) {
|
|
2192
|
-
showError(new Error("Status must be a number, or choose a preset"))
|
|
2193
|
-
return
|
|
2194
|
-
}
|
|
2195
|
-
api("POST", "/faults", body).then(renderFaults).catch(showError)
|
|
2196
|
-
})
|
|
2197
|
-
$("fault-clear").addEventListener("click", () => api("DELETE", "/faults").then(renderFaults).catch(showError))
|
|
2198
|
-
$("journal-clear").addEventListener("click", () => api("DELETE", "/requests").then(renderJournal).catch(showError))
|
|
2199
|
-
|
|
2200
|
-
$("route-list").addEventListener("click", (event) => {
|
|
2201
|
-
const button = event.target.closest("[data-route]")
|
|
2202
|
-
if (!button) return
|
|
2203
|
-
state.route = button.dataset.route
|
|
2204
|
-
$("route-title").textContent = state.route
|
|
2205
|
-
const method = state.route.split(" ")[0]
|
|
2206
|
-
$("route-body").disabled = method === "GET" || method === "DELETE"
|
|
2207
|
-
})
|
|
2208
|
-
$("route-form").addEventListener("submit", async (event) => {
|
|
2209
|
-
event.preventDefault()
|
|
2210
|
-
if (!state.route) return showError(new Error("Choose a route first"))
|
|
2211
|
-
const [method, path] = [state.route.split(" ")[0], state.route.split(" ").slice(1).join(" ")]
|
|
2212
|
-
const concrete = path.replace(/:([A-Za-z0-9_]+)/g, (_, key) => prompt("Value for " + key) || "")
|
|
2213
|
-
let body
|
|
2214
|
-
if (method !== "GET" && method !== "DELETE" && $("route-body").value.trim()) {
|
|
2215
|
-
try { body = JSON.parse($("route-body").value) }
|
|
2216
|
-
catch { return showError(new Error("Body is not JSON")) }
|
|
2217
|
-
}
|
|
2218
|
-
try {
|
|
2219
|
-
const data = await api(method, concrete, body)
|
|
2220
|
-
$("route-result").textContent = JSON.stringify(data, null, 2)
|
|
2221
|
-
} catch (error) {
|
|
2222
|
-
showError(error)
|
|
2223
|
-
}
|
|
2224
|
-
})
|
|
2225
|
-
|
|
2226
|
-
function mountPanels(panels) {
|
|
2227
|
-
const nav = $("nav")
|
|
2228
|
-
const views = $("views")
|
|
2229
|
-
for (const panel of panels) {
|
|
2230
|
-
const button = document.createElement("button")
|
|
2231
|
-
button.type = "button"
|
|
2232
|
-
button.dataset.view = panel.id
|
|
2233
|
-
button.textContent = panel.title
|
|
2234
|
-
nav.append(button)
|
|
2235
|
-
const section = document.createElement("section")
|
|
2236
|
-
section.className = "view"
|
|
2237
|
-
section.id = "view-" + panel.id
|
|
2238
|
-
section.hidden = true
|
|
2239
|
-
const title = document.createElement("h2")
|
|
2240
|
-
title.textContent = panel.title
|
|
2241
|
-
const lede = document.createElement("p")
|
|
2242
|
-
lede.className = "lede"
|
|
2243
|
-
lede.textContent = panel.description || ""
|
|
2244
|
-
const body = document.createElement("div")
|
|
2245
|
-
body.className = "panel-body"
|
|
2246
|
-
body.id = "panel-" + panel.id
|
|
2247
|
-
body.innerHTML = panel.html || ""
|
|
2248
|
-
section.append(title, lede, body)
|
|
2249
|
-
views.append(section)
|
|
2250
|
-
if (panel.script) {
|
|
2251
|
-
try {
|
|
2252
|
-
const run = new Function("root", "api", panel.script)
|
|
2253
|
-
run(body, panelApi)
|
|
2254
|
-
} catch (error) {
|
|
2255
|
-
const note = document.createElement("p")
|
|
2256
|
-
note.className = "banner show"
|
|
2257
|
-
note.textContent = error instanceof Error ? error.message : String(error)
|
|
2258
|
-
body.append(note)
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2264
|
-
loadNamespaces()
|
|
2265
|
-
.then(() => api("GET", "/ui/manifest"))
|
|
2266
|
-
.then((manifest) => { mountPanels((manifest && manifest.panels) || []); return refresh() })
|
|
2267
|
-
.catch(showError)
|
|
2268
|
-
})()
|
|
2269
|
-
</script>
|
|
3070
|
+
${script}
|
|
2270
3071
|
</body>
|
|
2271
3072
|
</html>`;
|
|
2272
3073
|
};
|
|
@@ -2283,10 +3084,10 @@ var json2 = (body) => new Response(JSON.stringify(body), {
|
|
|
2283
3084
|
});
|
|
2284
3085
|
var adminUiRoutes = (service, ui) => {
|
|
2285
3086
|
const shell = () => renderAdminDocument(service);
|
|
2286
|
-
const
|
|
3087
|
+
const document3 = () => ui?.render ? ui.render({ service, defaultHtml: shell }) : shell();
|
|
2287
3088
|
return {
|
|
2288
|
-
"GET /ui": () => htmlResponse(
|
|
2289
|
-
"GET /ui/": () => htmlResponse(
|
|
3089
|
+
"GET /ui": () => htmlResponse(document3()),
|
|
3090
|
+
"GET /ui/": () => htmlResponse(document3()),
|
|
2290
3091
|
"GET /ui/manifest": () => json2({
|
|
2291
3092
|
service,
|
|
2292
3093
|
panels: (ui?.panels ?? []).map((panel) => ({
|
|
@@ -2312,11 +3113,11 @@ var EXTENSION_KEYS = {
|
|
|
2312
3113
|
};
|
|
2313
3114
|
|
|
2314
3115
|
// ../../openapi/metadata/dist/read.js
|
|
2315
|
-
var
|
|
3116
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2316
3117
|
var extensionOf = (holder, key) => holder[key];
|
|
2317
3118
|
var operationMetadata = (operation) => {
|
|
2318
3119
|
const raw = extensionOf(operation, EXTENSION_KEYS.operation);
|
|
2319
|
-
const ext =
|
|
3120
|
+
const ext = isRecord3(raw) ? raw : {};
|
|
2320
3121
|
const supported = ext.supported ?? true;
|
|
2321
3122
|
const parity = ext.parity ?? {};
|
|
2322
3123
|
return {
|
|
@@ -2426,9 +3227,9 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
2426
3227
|
this.name = "OperationRegistryError";
|
|
2427
3228
|
}
|
|
2428
3229
|
};
|
|
2429
|
-
var verifyOperations = (
|
|
3230
|
+
var verifyOperations = (document3, handlers) => {
|
|
2430
3231
|
const problems = [];
|
|
2431
|
-
const operations = listOperations(
|
|
3232
|
+
const operations = listOperations(document3);
|
|
2432
3233
|
const seen = /* @__PURE__ */ new Set();
|
|
2433
3234
|
for (const operation of operations) {
|
|
2434
3235
|
if (seen.has(operation.operationId))
|
|
@@ -2544,7 +3345,7 @@ var SAMPLE = 25;
|
|
|
2544
3345
|
var WALK_LIMIT = 1e4;
|
|
2545
3346
|
var SKIP_CTORS = /* @__PURE__ */ new Set(["Hono", "HonoRequest", "Router"]);
|
|
2546
3347
|
var fail2 = (status, message) => ({ ok: false, status, message });
|
|
2547
|
-
var
|
|
3348
|
+
var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2548
3349
|
var kindOf = (value) => {
|
|
2549
3350
|
if (value === null)
|
|
2550
3351
|
return "null";
|
|
@@ -2643,7 +3444,7 @@ var absorb = (accum, value) => {
|
|
|
2643
3444
|
const kind = kindOf(value);
|
|
2644
3445
|
accum.kinds.add(kind);
|
|
2645
3446
|
accum.present++;
|
|
2646
|
-
if (!
|
|
3447
|
+
if (!isRecord4(value))
|
|
2647
3448
|
return;
|
|
2648
3449
|
for (const [key, child] of Object.entries(value)) {
|
|
2649
3450
|
const nested = accum.nested.get(key) ?? blankAccum(key);
|
|
@@ -2668,7 +3469,7 @@ var inferFields = (records) => {
|
|
|
2668
3469
|
if (records.length === 0)
|
|
2669
3470
|
return [];
|
|
2670
3471
|
const sample = records.slice(0, SAMPLE);
|
|
2671
|
-
const objects = sample.filter((record) =>
|
|
3472
|
+
const objects = sample.filter((record) => isRecord4(record.value));
|
|
2672
3473
|
if (objects.length === 0) {
|
|
2673
3474
|
const kinds = new Set(sample.map((record) => kindOf(record.value)));
|
|
2674
3475
|
const list = [...kinds];
|
|
@@ -2855,7 +3656,7 @@ var patchStateRecord = (scope, name, id, value) => {
|
|
|
2855
3656
|
const parsed = jsonValue(value);
|
|
2856
3657
|
if (!parsed.ok)
|
|
2857
3658
|
return parsed;
|
|
2858
|
-
const next =
|
|
3659
|
+
const next = isRecord4(current.value.value) && isRecord4(parsed.value) ? { ...current.value.value, ...parsed.value } : parsed.value;
|
|
2859
3660
|
return replaceStateRecord(scope, name, id, next);
|
|
2860
3661
|
};
|
|
2861
3662
|
var deleteStateRecord = (scope, name, id) => {
|
|
@@ -2923,7 +3724,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2923
3724
|
const scope = open(namespace);
|
|
2924
3725
|
if (!scope.ok)
|
|
2925
3726
|
return fromResult(scope, 201);
|
|
2926
|
-
if (!
|
|
3727
|
+
if (!isRecord4(body) || !("value" in body)) {
|
|
2927
3728
|
return adminError2(400, 'expected {"id?": "<id>", "value": <json>}');
|
|
2928
3729
|
}
|
|
2929
3730
|
const id = body.id;
|
|
@@ -2941,7 +3742,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2941
3742
|
const scope = open(namespace);
|
|
2942
3743
|
if (!scope.ok)
|
|
2943
3744
|
return fromResult(scope, 200);
|
|
2944
|
-
if (!
|
|
3745
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2945
3746
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2946
3747
|
const replaced = replaceStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2947
3748
|
if (replaced.ok)
|
|
@@ -2952,7 +3753,7 @@ var stateAdminRoutes = (routes) => {
|
|
|
2952
3753
|
const scope = open(namespace);
|
|
2953
3754
|
if (!scope.ok)
|
|
2954
3755
|
return fromResult(scope, 200);
|
|
2955
|
-
if (!
|
|
3756
|
+
if (!isRecord4(body) || !("value" in body))
|
|
2956
3757
|
return adminError2(400, 'expected {"value": <json>}');
|
|
2957
3758
|
const patched = patchStateRecord(scope.value, params.collection ?? "", params.id ?? "", body.value);
|
|
2958
3759
|
if (patched.ok)
|
|
@@ -2972,14 +3773,14 @@ var stateAdminRoutes = (routes) => {
|
|
|
2972
3773
|
};
|
|
2973
3774
|
|
|
2974
3775
|
// ../core/dist/version.js
|
|
2975
|
-
var PACKAGE_VERSION = true ? "1.
|
|
3776
|
+
var PACKAGE_VERSION = true ? "1.5.1" : UNRELEASED_VERSION;
|
|
2976
3777
|
|
|
2977
3778
|
// ../core/dist/webhooks.js
|
|
2978
3779
|
var json4 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
2979
3780
|
var adminError3 = (status, message) => json4(status, { error: { type: "mockingbird_admin", message } });
|
|
2980
|
-
var
|
|
3781
|
+
var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2981
3782
|
var parseEndpoint = (value) => {
|
|
2982
|
-
if (!
|
|
3783
|
+
if (!isRecord5(value) || typeof value.url !== "string")
|
|
2983
3784
|
return "each endpoint needs a url";
|
|
2984
3785
|
try {
|
|
2985
3786
|
new URL(value.url);
|
|
@@ -2996,12 +3797,12 @@ var parseEndpoint = (value) => {
|
|
|
2996
3797
|
const events = value.events ?? value.enabledEvents;
|
|
2997
3798
|
if (Array.isArray(events))
|
|
2998
3799
|
endpoint.events = events.map(String);
|
|
2999
|
-
if (
|
|
3800
|
+
if (isRecord5(value.tags)) {
|
|
3000
3801
|
endpoint.tags = Object.fromEntries(Object.entries(value.tags).map(([k, v]) => [k, String(v)]));
|
|
3001
3802
|
}
|
|
3002
3803
|
if (typeof value.account === "string")
|
|
3003
3804
|
endpoint.tags = { ...endpoint.tags, account: value.account };
|
|
3004
|
-
if (
|
|
3805
|
+
if (isRecord5(value.headers)) {
|
|
3005
3806
|
endpoint.headers = Object.fromEntries(Object.entries(value.headers).map(([k, v]) => [k, String(v)]));
|
|
3006
3807
|
}
|
|
3007
3808
|
return endpoint;
|
|
@@ -3028,7 +3829,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3028
3829
|
return json4(200, { status: "ok" });
|
|
3029
3830
|
},
|
|
3030
3831
|
"POST /webhooks/faults": ({ body, namespace }) => {
|
|
3031
|
-
if (!
|
|
3832
|
+
if (!isRecord5(body) || !["duplicate", "reorder", "drop"].includes(String(body.mode))) {
|
|
3032
3833
|
return adminError3(400, "mode must be duplicate, reorder or drop");
|
|
3033
3834
|
}
|
|
3034
3835
|
const fault = { mode: body.mode };
|
|
@@ -3044,7 +3845,7 @@ var webhookAdminRoutes = (hub) => ({
|
|
|
3044
3845
|
}))
|
|
3045
3846
|
}),
|
|
3046
3847
|
"PUT /webhook-endpoints": ({ body, namespace }) => {
|
|
3047
|
-
const list = Array.isArray(body) ? body :
|
|
3848
|
+
const list = Array.isArray(body) ? body : isRecord5(body) ? body.endpoints : void 0;
|
|
3048
3849
|
if (!Array.isArray(list))
|
|
3049
3850
|
return adminError3(400, "expected [{url, secret?, events?}]");
|
|
3050
3851
|
const parsed = [];
|
|
@@ -3114,8 +3915,8 @@ var DroppedConnectionError = class extends TypeError {
|
|
|
3114
3915
|
this.name = "TypeError";
|
|
3115
3916
|
}
|
|
3116
3917
|
};
|
|
3117
|
-
var operationMatcher = (
|
|
3118
|
-
const matchers = listOperations(
|
|
3918
|
+
var operationMatcher = (document3) => {
|
|
3919
|
+
const matchers = listOperations(document3).map((operation) => ({
|
|
3119
3920
|
operationId: operation.operationId,
|
|
3120
3921
|
method: operation.method.toUpperCase(),
|
|
3121
3922
|
pattern: new RegExp(`^${operation.path.split("/").map((segment) => segment.startsWith("{") ? "[^/]+" : segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("/")}/?$`),
|
|
@@ -3210,10 +4011,10 @@ var createRuntime = (options) => {
|
|
|
3210
4011
|
const ensureBranch = (namespace, branch2, at) => {
|
|
3211
4012
|
if (!BRANCH_PATTERN2.test(branch2))
|
|
3212
4013
|
throw new RangeError(`branch must match ${BRANCH_PATTERN2}`);
|
|
3213
|
-
const
|
|
4014
|
+
const history2 = timeline(namespace);
|
|
3214
4015
|
if (branch2 === "main") {
|
|
3215
4016
|
if (at !== void 0) {
|
|
3216
|
-
const point =
|
|
4017
|
+
const point = history2.checkout("main", at);
|
|
3217
4018
|
restoreNamespace(sqlite, storageNamespace(namespace), point.value.snapshot);
|
|
3218
4019
|
captured.set(namespace, point.value.snapshot);
|
|
3219
4020
|
rng.setState(point.value.rngState);
|
|
@@ -3226,10 +4027,10 @@ var createRuntime = (options) => {
|
|
|
3226
4027
|
return namespace;
|
|
3227
4028
|
}
|
|
3228
4029
|
const storage = physicalBranch(namespace, branch2);
|
|
3229
|
-
if (!
|
|
4030
|
+
if (!history2.hasBranch(branch2)) {
|
|
3230
4031
|
if (at === void 0)
|
|
3231
|
-
|
|
3232
|
-
const point =
|
|
4032
|
+
history2.commit(capture(namespace));
|
|
4033
|
+
const point = history2.fork(branch2, at === void 0 ? {} : { from: at });
|
|
3233
4034
|
const branchRng = createRng(options.seed ?? 0);
|
|
3234
4035
|
if (point)
|
|
3235
4036
|
branchRng.setState(point.value.rngState);
|
|
@@ -3238,8 +4039,8 @@ var createRuntime = (options) => {
|
|
|
3238
4039
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3239
4040
|
if (point)
|
|
3240
4041
|
captured.set(storage, point.value.snapshot);
|
|
3241
|
-
} else if (at !== void 0 &&
|
|
3242
|
-
const point =
|
|
4042
|
+
} else if (at !== void 0 && history2.head(branch2)?.id !== at) {
|
|
4043
|
+
const point = history2.checkout(branch2, at);
|
|
3243
4044
|
if (!instances.has(storage)) {
|
|
3244
4045
|
const branchRng = createRng(options.seed ?? 0);
|
|
3245
4046
|
branchRng.setState(point.value.rngState);
|
|
@@ -3250,7 +4051,7 @@ var createRuntime = (options) => {
|
|
|
3250
4051
|
captured.set(storage, point.value.snapshot);
|
|
3251
4052
|
} else {
|
|
3252
4053
|
if (!instances.has(storage)) {
|
|
3253
|
-
const point =
|
|
4054
|
+
const point = history2.head(branch2);
|
|
3254
4055
|
const branchRng = createRng(options.seed ?? 0);
|
|
3255
4056
|
if (point)
|
|
3256
4057
|
branchRng.setState(point.value.rngState);
|
|
@@ -3274,8 +4075,8 @@ var createRuntime = (options) => {
|
|
|
3274
4075
|
const checkout = (checkpointId, checkoutOptions = {}) => {
|
|
3275
4076
|
const namespace = checkoutOptions.namespace ?? DEFAULT_NAMESPACE;
|
|
3276
4077
|
const branchName = checkoutOptions.branch ?? "main";
|
|
3277
|
-
const
|
|
3278
|
-
const point =
|
|
4078
|
+
const history2 = timeline(namespace);
|
|
4079
|
+
const point = history2.checkout(branchName, checkpointId);
|
|
3279
4080
|
const storage = ensureBranch(namespace, branchName);
|
|
3280
4081
|
restoreNamespace(sqlite, storageNamespace(storage), point.value.snapshot);
|
|
3281
4082
|
captured.set(storage, point.value.snapshot);
|
|
@@ -3325,9 +4126,9 @@ var createRuntime = (options) => {
|
|
|
3325
4126
|
instance(name);
|
|
3326
4127
|
restoreNamespace(sqlite, storageNamespace(name), from);
|
|
3327
4128
|
captured.set(name, from);
|
|
3328
|
-
const
|
|
3329
|
-
if (
|
|
3330
|
-
|
|
4129
|
+
const history2 = timelines.get(name);
|
|
4130
|
+
if (history2)
|
|
4131
|
+
history2.commit(capture(name), { branch: "main" });
|
|
3331
4132
|
else
|
|
3332
4133
|
timeline(name);
|
|
3333
4134
|
};
|
|
@@ -3556,10 +4357,10 @@ var createRuntime = (options) => {
|
|
|
3556
4357
|
},
|
|
3557
4358
|
release: (name, checkpointId) => timeline(name).release(checkpointId),
|
|
3558
4359
|
inspect: (name) => {
|
|
3559
|
-
const
|
|
4360
|
+
const history2 = timeline(name);
|
|
3560
4361
|
return {
|
|
3561
|
-
branches:
|
|
3562
|
-
checkpoints:
|
|
4362
|
+
branches: history2.branches(),
|
|
4363
|
+
checkpoints: history2.checkpoints().map(({ id, branch: branchName, parent, at }) => ({
|
|
3563
4364
|
id,
|
|
3564
4365
|
branch: branchName,
|
|
3565
4366
|
parent,
|
|
@@ -3574,11 +4375,11 @@ var createRuntime = (options) => {
|
|
|
3574
4375
|
} : {},
|
|
3575
4376
|
routes: {
|
|
3576
4377
|
...credentialRoutes(credentials),
|
|
3577
|
-
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3578
4378
|
...options.webhooks ? webhookAdminRoutes(options.webhooks) : {},
|
|
3579
4379
|
...options.admin?.(runtime) ?? {},
|
|
3580
|
-
//
|
|
3581
|
-
//
|
|
4380
|
+
// Shell routes win a colliding key. Fault presets are one of them: the admin page
|
|
4381
|
+
// maps `presets`, and a service route must not replace that list with a record.
|
|
4382
|
+
...options.presets ? presetRoutes(options.presets, runtime) : {},
|
|
3582
4383
|
...stateAdminRoutes({
|
|
3583
4384
|
open: stateScope,
|
|
3584
4385
|
afterWrite: (namespace) => {
|
|
@@ -3653,9 +4454,12 @@ var credentialRoutes = (registry) => ({
|
|
|
3653
4454
|
}
|
|
3654
4455
|
});
|
|
3655
4456
|
var presetRoutes = (presets, runtime) => ({
|
|
3656
|
-
"GET /faults/presets": () =>
|
|
3657
|
-
|
|
3658
|
-
|
|
4457
|
+
"GET /faults/presets": () => {
|
|
4458
|
+
const body = {
|
|
4459
|
+
presets: Object.entries(presets).map(([name, preset]) => ({ name, ...preset }))
|
|
4460
|
+
};
|
|
4461
|
+
return adminJson(200, body);
|
|
4462
|
+
},
|
|
3659
4463
|
"POST /faults/presets/:name": ({ params, body, namespace }) => {
|
|
3660
4464
|
const name = params.name;
|
|
3661
4465
|
if (!presets[name])
|
|
@@ -3666,7 +4470,7 @@ var presetRoutes = (presets, runtime) => ({
|
|
|
3666
4470
|
});
|
|
3667
4471
|
|
|
3668
4472
|
// src/generated/openapi.ts
|
|
3669
|
-
var document = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Plane REST API (Mockingbird subset)","description":"Stateful mock subset of Plane's public REST API v1 for one project: work items (cursor-paginated list, get, create, patch), comments, links, states and labels. Hand-authored from Plane's API reference and the consumer's zod schemas (plane-response.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Shapes from https://developers.plane.so/api-reference; consumer: apps/backend/src/modules/bug-reports/adapters/native-fetch-plane.adapter.ts, plane-http-client.ts, plane-response.ts."}},"servers":[{"url":"https://api.plane.so"}],"security":[{"apiKey":[]}],"paths":{"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/":{"get":{"operationId":"ListWorkItems","description":"Cursor-paginated work items (\`cursor=<per_page>:<page>:<is_prev>\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItemPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/OrderBy"}]},"post":{"operationId":"CreateWorkItem","description":"Create a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkItemBody"}}}}},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"GetWorkItem","description":"One work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The work item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}}},"patch":{"operationId":"UpdateWorkItem","description":"Partial update (the consumer sends \`state\` or \`labels\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkItemBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"ListComments","description":"Comments on a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateComment","description":"Add a comment.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommentBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"ListLinks","description":"Links on a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateLink","description":"Attach a link (the same URL twice is a 400).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The URL is already linked (carries the existing link id)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/states/":{"get":{"operationId":"ListStates","description":"The project's workflow states.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatePage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]},"/api/v1/workspaces/{slug}/projects/{project_id}/labels/":{"get":{"operationId":"ListLabels","description":"The project's labels.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateLabel","description":"Create a label (a duplicate name is a 409 carrying the existing id).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Label"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Duplicate name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLabelBody"}}}}},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"parameters":{"Slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string","enum":["acme"]}},"ProjectId":{"name":"project_id","in":"path","required":true,"schema":{"type":"string","enum":["33333333-3333-4333-8333-333333333333"]}},"WorkItemId":{"name":"work_item_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"work_item","missing":"00000000-0000-4000-8000-000000000000"}}},"PerPage":{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},"Cursor":{"name":"cursor","in":"query","required":false,"schema":{"type":"string","enum":["2:0:0","2:1:0","2:2:0","100:0:0"]}},"OrderBy":{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["-created_at","created_at","sequence_id","-sequence_id","name","-name"]}}},"responses":{"Unauthorized":{"description":"Missing or invalid X-API-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"NotFound":{"description":"Unknown workspace, project or item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"Throttled":{"description":"Rate limited (60 requests per minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"schemas":{"WorkItem":{"type":"object","required":["id","sequence_id","name","description_html","state","labels","priority","created_at","updated_at","project","workspace"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"work_item","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"sequence_id":{"type":"integer"},"name":{"type":"string"},"description_html":{"type":"string"},"description_stripped":{"type":["string","null"]},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]},"state":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"labels":{"type":"array","items":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}},"assignees":{"type":"array","items":{"type":"string"}},"parent":{"type":["string","null"]},"start_date":{"type":["string","null"]},"target_date":{"type":["string","null"]},"completed_at":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"archived_at":{"type":["string","null"]},"is_draft":{"type":"boolean"},"sort_order":{"type":"number"},"project":{"type":"string"},"workspace":{"type":"string"},"created_by":{"type":["string","null"]},"updated_by":{"type":["string","null"]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"State":{"type":"object","required":["id","name","group","color","sequence","default"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"state","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"group":{"type":"string","enum":["backlog","unstarted","started","completed","cancelled","triage"]},"color":{"type":"string"},"sequence":{"type":"number"},"default":{"type":"boolean"},"description":{"type":"string"},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Label":{"type":"object","required":["id","name","color"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"label","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"color":{"type":"string"},"description":{"type":"string"},"parent":{"type":["string","null"]},"sort_order":{"type":"number"},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Comment":{"type":"object","required":["id","comment_html","issue","created_at"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-volatile":{"kind":"id"}},"comment_html":{"type":"string"},"comment_stripped":{"type":"string"},"access":{"type":"string"},"issue":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"actor":{"type":["string","null"]},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Link":{"type":"object","required":["id","url","title","issue","created_at"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-volatile":{"kind":"id"}},"url":{"type":"string"},"title":{"type":["string","null"]},"metadata":{"type":"object"},"issue":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"WorkItemPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/WorkItem"}}}},"StatePage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/State"}}}},"LabelPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Label"}}}},"CommentPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}}}},"LinkPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}},"CreateWorkItemBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description_html":{"type":"string","maxLength":2000},"state":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"state","missing":"00000000-0000-4000-8000-000000000000"}},"labels":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"label","missing":"00000000-0000-4000-8000-000000000000"}}},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]}}},"UpdateWorkItemBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description_html":{"type":"string","maxLength":2000},"state":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"state","missing":"00000000-0000-4000-8000-000000000000"}},"labels":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"label","missing":"00000000-0000-4000-8000-000000000000"}}},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]}}},"CreateCommentBody":{"type":"object","required":["comment_html"],"properties":{"comment_html":{"type":"string","minLength":1,"maxLength":2000},"access":{"type":"string","enum":["INTERNAL","EXTERNAL"]}}},"CreateLinkBody":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","pattern":"^https://","maxLength":200},"title":{"type":"string","minLength":1,"maxLength":255}}},"CreateLabelBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"description":{"type":"string","maxLength":255}}},"ErrorBody":{"type":"object","properties":{"error":{"type":"string"},"detail":{"type":"string"},"id":{"type":"string"}}},"ValidationError":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}`);
|
|
4473
|
+
var document2 = JSON.parse(`{"openapi":"3.1.0","info":{"title":"Plane REST API (Mockingbird subset)","description":"Stateful mock subset of Plane's public REST API v1 for one project: work items (cursor-paginated list, get, create, patch), comments, links, states and labels. Hand-authored from Plane's API reference and the consumer's zod schemas (plane-response.ts).\\n","version":"1","x-mockingbird-upstream":{"note":"Shapes from https://developers.plane.so/api-reference; consumer: apps/backend/src/modules/bug-reports/adapters/native-fetch-plane.adapter.ts, plane-http-client.ts, plane-response.ts."}},"servers":[{"url":"https://api.plane.so"}],"security":[{"apiKey":[]}],"paths":{"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/":{"get":{"operationId":"ListWorkItems","description":"Cursor-paginated work items (\`cursor=<per_page>:<page>:<is_prev>\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItemPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"},{"$ref":"#/components/parameters/OrderBy"}]},"post":{"operationId":"CreateWorkItem","description":"Create a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWorkItemBody"}}}}},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"GetWorkItem","description":"One work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"The work item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}}},"patch":{"operationId":"UpdateWorkItem","description":"Partial update (the consumer sends \`state\` or \`labels\`).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkItem"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWorkItemBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/comments/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"ListComments","description":"Comments on a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateComment","description":"Add a comment.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Comment"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommentBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/work-items/{work_item_id}/links/":{"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/WorkItemId"}],"get":{"operationId":"ListLinks","description":"Links on a work item.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateLink","description":"Attach a link (the same URL twice is a 400).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Link"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The URL is already linked (carries the existing link id)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkBody"}}}}}},"/api/v1/workspaces/{slug}/projects/{project_id}/states/":{"get":{"operationId":"ListStates","description":"The project's workflow states.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatePage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]},"/api/v1/workspaces/{slug}/projects/{project_id}/labels/":{"get":{"operationId":"ListLabels","description":"The project's labels.","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":true}},"responses":{"200":{"description":"A page","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LabelPage"}}}},"400":{"description":"Invalid cursor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/Throttled"}},"parameters":[{"$ref":"#/components/parameters/PerPage"},{"$ref":"#/components/parameters/Cursor"}]},"post":{"operationId":"CreateLabel","description":"Create a label (a duplicate name is a 409 carrying the existing id).","x-mockingbird":{"supported":true,"parity":{"enabled":true,"safe":false}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Label"}}}},"400":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Duplicate name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"$ref":"#/components/responses/Throttled"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLabelBody"}}}}},"parameters":[{"$ref":"#/components/parameters/Slug"},{"$ref":"#/components/parameters/ProjectId"}]}},"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"parameters":{"Slug":{"name":"slug","in":"path","required":true,"schema":{"type":"string","enum":["acme"]}},"ProjectId":{"name":"project_id","in":"path","required":true,"schema":{"type":"string","enum":["33333333-3333-4333-8333-333333333333"]}},"WorkItemId":{"name":"work_item_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"work_item","missing":"00000000-0000-4000-8000-000000000000"}}},"PerPage":{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}},"Cursor":{"name":"cursor","in":"query","required":false,"schema":{"type":"string","enum":["2:0:0","2:1:0","2:2:0","100:0:0"]}},"OrderBy":{"name":"order_by","in":"query","required":false,"schema":{"type":"string","enum":["-created_at","created_at","sequence_id","-sequence_id","name","-name"]}}},"responses":{"Unauthorized":{"description":"Missing or invalid X-API-Key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"NotFound":{"description":"Unknown workspace, project or item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"Throttled":{"description":"Rate limited (60 requests per minute per key)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}},"schemas":{"WorkItem":{"type":"object","required":["id","sequence_id","name","description_html","state","labels","priority","created_at","updated_at","project","workspace"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"work_item","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"sequence_id":{"type":"integer"},"name":{"type":"string"},"description_html":{"type":"string"},"description_stripped":{"type":["string","null"]},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]},"state":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"labels":{"type":"array","items":{"type":"string","x-mockingbird-volatile":{"kind":"id"}}},"assignees":{"type":"array","items":{"type":"string"}},"parent":{"type":["string","null"]},"start_date":{"type":["string","null"]},"target_date":{"type":["string","null"]},"completed_at":{"type":["string","null"],"x-mockingbird-volatile":{"kind":"timestamp"}},"archived_at":{"type":["string","null"]},"is_draft":{"type":"boolean"},"sort_order":{"type":"number"},"project":{"type":"string"},"workspace":{"type":"string"},"created_by":{"type":["string","null"]},"updated_by":{"type":["string","null"]},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"State":{"type":"object","required":["id","name","group","color","sequence","default"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"state","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"group":{"type":"string","enum":["backlog","unstarted","started","completed","cancelled","triage"]},"color":{"type":"string"},"sequence":{"type":"number"},"default":{"type":"boolean"},"description":{"type":"string"},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Label":{"type":"object","required":["id","name","color"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-resource":{"type":"label","identity":true},"x-mockingbird-volatile":{"kind":"id"}},"name":{"type":"string"},"color":{"type":"string"},"description":{"type":"string"},"parent":{"type":["string","null"]},"sort_order":{"type":"number"},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Comment":{"type":"object","required":["id","comment_html","issue","created_at"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-volatile":{"kind":"id"}},"comment_html":{"type":"string"},"comment_stripped":{"type":"string"},"access":{"type":"string"},"issue":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"actor":{"type":["string","null"]},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"Link":{"type":"object","required":["id","url","title","issue","created_at"],"properties":{"id":{"type":"string","format":"uuid","x-mockingbird-volatile":{"kind":"id"}},"url":{"type":"string"},"title":{"type":["string","null"]},"metadata":{"type":"object"},"issue":{"type":"string","x-mockingbird-volatile":{"kind":"id"}},"project":{"type":"string"},"workspace":{"type":"string"},"created_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}},"updated_at":{"type":"string","x-mockingbird-volatile":{"kind":"timestamp"}}}},"WorkItemPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/WorkItem"}}}},"StatePage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/State"}}}},"LabelPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Label"}}}},"CommentPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Comment"}}}},"LinkPage":{"type":"object","required":["results","next_cursor","prev_cursor","next_page_results","prev_page_results","count","total_count","total_pages","total_results"],"properties":{"grouped_by":{"type":["string","null"]},"sub_grouped_by":{"type":["string","null"]},"total_count":{"type":"integer"},"next_cursor":{"type":"string"},"prev_cursor":{"type":"string"},"next_page_results":{"type":"boolean"},"prev_page_results":{"type":"boolean"},"count":{"type":"integer"},"total_pages":{"type":"integer"},"total_results":{"type":"integer"},"extra_stats":{"type":["object","null"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/Link"}}}},"CreateWorkItemBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description_html":{"type":"string","maxLength":2000},"state":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"state","missing":"00000000-0000-4000-8000-000000000000"}},"labels":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"label","missing":"00000000-0000-4000-8000-000000000000"}}},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]}}},"UpdateWorkItemBody":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255},"description_html":{"type":"string","maxLength":2000},"state":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"state","missing":"00000000-0000-4000-8000-000000000000"}},"labels":{"type":"array","maxItems":3,"items":{"type":"string","format":"uuid","x-mockingbird-resource-ref":{"type":"label","missing":"00000000-0000-4000-8000-000000000000"}}},"priority":{"type":"string","enum":["urgent","high","medium","low","none"]}}},"CreateCommentBody":{"type":"object","required":["comment_html"],"properties":{"comment_html":{"type":"string","minLength":1,"maxLength":2000},"access":{"type":"string","enum":["INTERNAL","EXTERNAL"]}}},"CreateLinkBody":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","pattern":"^https://","maxLength":200},"title":{"type":"string","minLength":1,"maxLength":255}}},"CreateLabelBody":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"color":{"type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"description":{"type":"string","maxLength":255}}},"ErrorBody":{"type":"object","properties":{"error":{"type":"string"},"detail":{"type":"string"},"id":{"type":"string"}}},"ValidationError":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}`);
|
|
3670
4474
|
var operationIds = ["ListWorkItems", "CreateWorkItem", "GetWorkItem", "UpdateWorkItem", "ListComments", "CreateComment", "ListLinks", "CreateLink", "ListStates", "ListLabels", "CreateLabel"];
|
|
3671
4475
|
var supportedOperationIds = ["ListWorkItems", "CreateWorkItem", "GetWorkItem", "UpdateWorkItem", "ListComments", "CreateComment", "ListLinks", "CreateLink", "ListStates", "ListLabels", "CreateLabel"];
|
|
3672
4476
|
|
|
@@ -3862,18 +4666,18 @@ form.addEventListener("submit", (event) => {
|
|
|
3862
4666
|
`.trim();
|
|
3863
4667
|
var json5 = (status, body) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
|
3864
4668
|
var adminError4 = (status, message) => json5(status, { error: { type: "mockingbird_admin", message } });
|
|
3865
|
-
var
|
|
4669
|
+
var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3866
4670
|
var adminRoutes = (runtime) => ({
|
|
3867
4671
|
"GET /work-items": ({ namespace }) => json5(200, { work_items: runtime.instance(namespace).workItems() }),
|
|
3868
4672
|
"POST /work-items/:id/state": ({ params, body, namespace }) => {
|
|
3869
|
-
if (!
|
|
4673
|
+
if (!isRecord6(body) || typeof body.state !== "string") {
|
|
3870
4674
|
return adminError4(400, 'expected {"state": "<state id or name, e.g. Done>"}');
|
|
3871
4675
|
}
|
|
3872
4676
|
const item = runtime.instance(namespace).moveToState(params.id, body.state);
|
|
3873
4677
|
return item ? json5(200, item) : adminError4(404, `no work item ${params.id} or state ${body.state}`);
|
|
3874
4678
|
},
|
|
3875
4679
|
"POST /projects": ({ body, namespace }) => {
|
|
3876
|
-
if (!
|
|
4680
|
+
if (!isRecord6(body) || typeof body.workspace !== "string" || typeof body.project !== "string") {
|
|
3877
4681
|
return adminError4(400, 'expected {"workspace": "<slug>", "project": "<uuid>"}');
|
|
3878
4682
|
}
|
|
3879
4683
|
const api = runtime.instance(namespace);
|
|
@@ -3882,7 +4686,7 @@ var adminRoutes = (runtime) => ({
|
|
|
3882
4686
|
},
|
|
3883
4687
|
"GET /settings": ({ namespace }) => json5(200, runtime.instance(namespace).state.current()),
|
|
3884
4688
|
"PUT /settings": ({ body, namespace }) => {
|
|
3885
|
-
if (!
|
|
4689
|
+
if (!isRecord6(body)) return adminError4(400, "expected a JSON object");
|
|
3886
4690
|
const patch = {};
|
|
3887
4691
|
if (body.apiKeys !== void 0) {
|
|
3888
4692
|
if (!Array.isArray(body.apiKeys)) return adminError4(400, "apiKeys: string[]");
|
|
@@ -3903,7 +4707,7 @@ var adminRoutes = (runtime) => ({
|
|
|
3903
4707
|
});
|
|
3904
4708
|
var createRuntime2 = (options = {}) => createRuntime({
|
|
3905
4709
|
name: PLANE_NAMESPACE,
|
|
3906
|
-
document,
|
|
4710
|
+
document: document2,
|
|
3907
4711
|
...options.sqlite ? { sqlite: options.sqlite } : {},
|
|
3908
4712
|
...options.clock ? { clock: options.clock } : {},
|
|
3909
4713
|
...options.seed !== void 0 ? { seed: options.seed } : {},
|
|
@@ -3994,7 +4798,7 @@ var PlaneAPI = class {
|
|
|
3994
4798
|
CreateLabel: (context) => this.createLabel(context)
|
|
3995
4799
|
});
|
|
3996
4800
|
this.service = createService({
|
|
3997
|
-
document,
|
|
4801
|
+
document: document2,
|
|
3998
4802
|
handlers,
|
|
3999
4803
|
sqlite,
|
|
4000
4804
|
namespace,
|
|
@@ -4328,7 +5132,7 @@ var PlaneAPI = class {
|
|
|
4328
5132
|
};
|
|
4329
5133
|
|
|
4330
5134
|
export {
|
|
4331
|
-
document,
|
|
5135
|
+
document2 as document,
|
|
4332
5136
|
operationIds,
|
|
4333
5137
|
supportedOperationIds,
|
|
4334
5138
|
DEFAULT_STATES,
|
|
@@ -4339,4 +5143,4 @@ export {
|
|
|
4339
5143
|
apiKeyCredential,
|
|
4340
5144
|
PlaneAPI
|
|
4341
5145
|
};
|
|
4342
|
-
//# sourceMappingURL=chunk-
|
|
5146
|
+
//# sourceMappingURL=chunk-X3BPHZRL.js.map
|