create-safest-tools 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/console/registry/RegistryDetailsDialog.tsx +249 -0
- package/template/console/registry/RegistryDialog.tsx +116 -62
- package/template/console/registry/RegistryWorkspace.tsx +75 -55
- package/template/console/registry/types.ts +23 -1
- package/template/console/reports/ReportDrawer.tsx +1 -10
- package/template/console/shell/navigation.ts +1 -1
- package/template/package.json +2 -2
- package/template/public/console/auth-shell.js +8931 -8348
- package/template/public/styles.css +201 -7
- package/template/src/component-executor.ts +4 -1
- package/template/src/platform-registry.ts +19 -9
- package/template/src/report-email-inbound.ts +11 -4
- package/template/src/report-email.ts +12 -9
- package/template/src/report-repository.ts +6 -1
|
@@ -296,10 +296,13 @@ td small { margin-top: 3px; color: var(--muted); }
|
|
|
296
296
|
.report-run-record strong { font-size: 10px; }
|
|
297
297
|
.report-run-record small { margin-top: 4px; color: var(--muted); font-size: 8px; overflow-wrap: anywhere; }
|
|
298
298
|
.report-timeline { position: relative; display: grid; gap: 0; }
|
|
299
|
-
.timeline-event { position: relative; display: grid; grid-template-columns:
|
|
300
|
-
.timeline-event:not(:last-child)::before { content: ""; position: absolute; top:
|
|
301
|
-
.timeline-marker { position: relative; z-index: 1; width:
|
|
302
|
-
.timeline-marker.
|
|
299
|
+
.timeline-event { position: relative; display: grid; grid-template-columns: 16px 1fr; gap: 12px; min-height: 48px; }
|
|
300
|
+
.timeline-event:not(:last-child)::before { content: ""; position: absolute; top: 14px; bottom: 0; left: 5px; width: 2px; background: #d8e1da; }
|
|
301
|
+
.timeline-marker { position: relative; z-index: 1; width: 12px; height: 12px; margin-top: 4px; border: 3px solid #fff; border-radius: 50%; background: #527261; box-shadow: 0 0 0 1px #b9cabe; }
|
|
302
|
+
.timeline-marker.message { background: #2f6d91; box-shadow: 0 0 0 1px #a9c9da; }
|
|
303
|
+
.timeline-marker.decision { background: #245d43; box-shadow: 0 0 0 1px #9fc2ad; }
|
|
304
|
+
.timeline-marker.finding { background: #7a6598; box-shadow: 0 0 0 1px #c5b9d5; }
|
|
305
|
+
.timeline-marker.action { background: #a35b27; box-shadow: 0 0 0 1px #e0b48f; }
|
|
303
306
|
.timeline-event > div { padding: 2px 0 10px; }
|
|
304
307
|
.timeline-event strong, .timeline-event span, .timeline-event small { display: block; }
|
|
305
308
|
.timeline-event strong { font-size: 9px; }
|
|
@@ -1161,9 +1164,9 @@ body.login-mode .top-actions { margin-left: auto; }
|
|
|
1161
1164
|
.case-activity-heading p { margin: 5px 0 0; color: var(--muted); font-size: 12px; line-height: 1.5; }
|
|
1162
1165
|
.case-activity-heading > span { flex: 0 0 auto; padding: 5px 9px; border-radius: 999px; color: #315b47; background: #edf5ef; font-size: 11px; font-weight: 750; }
|
|
1163
1166
|
.case-activity-section .report-timeline { padding-left: 2px; }
|
|
1164
|
-
.case-activity-section .timeline-event { grid-template-columns:
|
|
1165
|
-
.case-activity-section .timeline-event:not(:last-child)::before { top:
|
|
1166
|
-
.case-activity-section .timeline-marker { width:
|
|
1167
|
+
.case-activity-section .timeline-event { grid-template-columns: 18px minmax(0, 1fr); gap: 14px; min-height: 68px; }
|
|
1168
|
+
.case-activity-section .timeline-event:not(:last-child)::before { top: 16px; bottom: 0; left: 6px; width: 2px; background: #dfe8e1; }
|
|
1169
|
+
.case-activity-section .timeline-marker { width: 14px; height: 14px; margin-top: 3px; }
|
|
1167
1170
|
.case-activity-section .timeline-event > div { padding: 2px 0 18px; }
|
|
1168
1171
|
.case-activity-section .timeline-event strong { color: #172d23; font-size: 13px; line-height: 1.35; }
|
|
1169
1172
|
.case-activity-section .timeline-event span { margin-top: 4px; color: #40584c; font-size: 12px; line-height: 1.5; }
|
|
@@ -1596,3 +1599,194 @@ body.login-mode .top-actions { margin-left: auto; }
|
|
|
1596
1599
|
.builder-row-actions { justify-content: flex-end; }
|
|
1597
1600
|
.field-option-row button { width: 100%; }
|
|
1598
1601
|
}
|
|
1602
|
+
|
|
1603
|
+
/* Tools and connections */
|
|
1604
|
+
.registry-workspace { max-width: 1400px; margin-inline: auto; }
|
|
1605
|
+
.registry-workspace > .page-head { margin-inline: 0; }
|
|
1606
|
+
.registry-model { margin-bottom: 20px; padding: 16px 18px; display: grid; grid-template-columns: minmax(150px,1fr) auto minmax(150px,1fr) auto minmax(150px,1fr) auto minmax(150px,1fr); align-items: center; gap: 12px; border: 1px solid #d8e3da; border-radius: 13px; background: linear-gradient(135deg, #fbfdf9, #f1f7f1); }
|
|
1607
|
+
.registry-model > div { min-width: 0; display: grid; grid-template-columns: 28px minmax(0,1fr); column-gap: 10px; align-items: center; }
|
|
1608
|
+
.registry-model > div > span { width: 28px; height: 28px; grid-row: span 2; display: grid; place-items: center; border-radius: 8px; color: var(--forest); background: white; box-shadow: 0 0 0 1px #d7e1d9; font-size: 10px; font-weight: 850; }
|
|
1609
|
+
.registry-model strong { font-size: 12px; }
|
|
1610
|
+
.registry-model small { margin-top: 2px; overflow: hidden; color: var(--muted); font-size: 10px; line-height: 1.35; text-overflow: ellipsis; white-space: nowrap; }
|
|
1611
|
+
.registry-model > b { color: #8ba095; font-size: 15px; }
|
|
1612
|
+
.registry-tabs-row { margin-bottom: 13px; display: flex; align-items: center; gap: 16px; }
|
|
1613
|
+
.registry-tabs-row .segmented-tabs { flex: 0 0 auto; margin: 0; }
|
|
1614
|
+
.registry-tabs-row .segmented-tabs button { font-size: 11px; }
|
|
1615
|
+
.registry-tabs-row .segmented-tabs button span { min-width: 20px; margin-left: 4px; padding: 2px 6px; border-radius: 999px; color: #5b6a61; background: #e5eae5; font-size: 9px; }
|
|
1616
|
+
.registry-tabs-row .segmented-tabs button.active span { color: var(--forest); background: var(--mint); }
|
|
1617
|
+
.registry-tabs-row > p { max-width: 720px; margin: 0; color: var(--muted); font-size: 11px; line-height: 1.5; }
|
|
1618
|
+
.registry-toolbar { margin-bottom: 12px; display: flex; align-items: center; gap: 10px; }
|
|
1619
|
+
.registry-toolbar .search-field { position: relative; min-width: 230px; flex: 1 1 340px; }
|
|
1620
|
+
.registry-toolbar .search-field > span { position: absolute; z-index: 1; top: 11px; left: 12px; color: #849087; font-size: 14px; }
|
|
1621
|
+
.registry-toolbar .search-field input { padding-left: 34px; }
|
|
1622
|
+
.registry-filter-chips { display: flex; align-items: center; gap: 5px; }
|
|
1623
|
+
.registry-filter-chips button { min-height: 34px; padding: 7px 10px; border: 1px solid var(--line); border-radius: 999px; color: #58665e; background: white; font-size: 10px; font-weight: 750; }
|
|
1624
|
+
.registry-filter-chips button:hover, .registry-filter-chips button.active { color: var(--forest-dark); border-color: #a8c2b0; background: #eef6f0; }
|
|
1625
|
+
.registry-archived-toggle { margin-left: auto; display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 10px; white-space: nowrap; }
|
|
1626
|
+
.registry-archived-toggle input { width: 15px; min-height: 15px; accent-color: var(--forest); }
|
|
1627
|
+
.registry-resource-list { min-width: 0; border: 1px solid var(--line); border-radius: 13px; background: white; box-shadow: 0 4px 16px rgba(21,55,37,.035); overflow: hidden; }
|
|
1628
|
+
.registry-resource-list > .skeleton-card { min-height: 118px; border-radius: 0; border-bottom: 1px solid var(--line); }
|
|
1629
|
+
.registry-resource-row { min-width: 0; padding: 17px 18px; display: grid; grid-template-columns: 42px minmax(280px,1fr) 92px minmax(150px,190px) auto; align-items: center; gap: 14px; border-top: 1px solid #edf0ec; }
|
|
1630
|
+
.registry-resource-row:first-child { border-top: 0; }
|
|
1631
|
+
.registry-resource-row:hover { background: #fbfcfa; }
|
|
1632
|
+
.registry-resource-row .object-icon { width: 38px; height: 38px; font-size: 15px; }
|
|
1633
|
+
.registry-resource-main { min-width: 0; }
|
|
1634
|
+
.registry-resource-title { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; }
|
|
1635
|
+
.registry-resource-title h3 { margin: 0; font: 720 15px/1.25 var(--sans); letter-spacing: -.015em; }
|
|
1636
|
+
.registry-owner-badge { padding: 3px 6px; border-radius: 999px; color: #5c4b83; background: #f0edfa; font-size: 8px; font-weight: 800; }
|
|
1637
|
+
.registry-resource-main > p { max-width: 720px; margin: 5px 0 8px; overflow: hidden; color: var(--muted); font-size: 11px; line-height: 1.45; text-overflow: ellipsis; white-space: nowrap; }
|
|
1638
|
+
.registry-resource-tags { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
1639
|
+
.registry-resource-tags span { padding: 3px 6px; border-radius: 5px; color: #5c6961; background: #f0f3ef; font-size: 8px; font-weight: 700; }
|
|
1640
|
+
.registry-resource-relationship span, .registry-resource-relationship strong, .registry-resource-relationship small { display: block; }
|
|
1641
|
+
.registry-resource-relationship span { color: var(--muted); font-size: 8px; text-transform: uppercase; }
|
|
1642
|
+
.registry-resource-relationship strong { margin-top: 3px; font-size: 15px; }
|
|
1643
|
+
.registry-resource-relationship small { color: var(--muted); font-size: 8px; }
|
|
1644
|
+
.registry-resource-state { min-width: 0; }
|
|
1645
|
+
.registry-status { width: fit-content; padding: 4px 8px; display: inline-flex; align-items: center; gap: 6px; border-radius: 999px; color: #526159; background: #edf1ed; font-size: 9px; font-weight: 800; }
|
|
1646
|
+
.registry-status i { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
|
|
1647
|
+
.registry-status.available { color: #2f6845; background: #eaf5ed; }
|
|
1648
|
+
.registry-status.paused { color: #896221; background: #fff3d8; }
|
|
1649
|
+
.registry-status.archived { color: #69736d; background: #ecefed; }
|
|
1650
|
+
.registry-resource-state small { margin-top: 6px; display: block; overflow: hidden; color: var(--muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
|
|
1651
|
+
.registry-resource-row > button { justify-self: end; white-space: nowrap; }
|
|
1652
|
+
.registry-resource-list > .empty-product { border: 0; border-radius: 0; }
|
|
1653
|
+
|
|
1654
|
+
.registry-detail-dialog { width: min(820px, calc(100% - 30px)); }
|
|
1655
|
+
.registry-detail-head h2 { margin-top: 2px; font-family: var(--sans); font-size: 21px; }
|
|
1656
|
+
.registry-detail-head p:last-child { max-width: 650px; font-size: 11px; line-height: 1.45; text-transform: none; }
|
|
1657
|
+
.registry-detail-body { padding: 20px 22px 24px; display: grid; gap: 13px; }
|
|
1658
|
+
.registry-detail-summary { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 8px; }
|
|
1659
|
+
.registry-detail-summary > div { min-width: 0; padding: 11px; border-radius: 9px; background: #f5f7f4; }
|
|
1660
|
+
.registry-detail-summary span, .registry-detail-summary strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1661
|
+
.registry-detail-summary span { color: var(--muted); font-size: 8px; text-transform: uppercase; }
|
|
1662
|
+
.registry-detail-summary strong { margin-top: 5px; font-size: 10px; }
|
|
1663
|
+
.registry-detail-section { padding: 15px; border: 1px solid var(--line); border-radius: 11px; }
|
|
1664
|
+
.registry-section-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
|
|
1665
|
+
.registry-section-heading h3 { margin: 0; font-size: 13px; }
|
|
1666
|
+
.registry-section-heading p { max-width: 580px; margin: 4px 0 0; color: var(--muted); font-size: 10px; line-height: 1.5; }
|
|
1667
|
+
.registry-section-heading button { flex: 0 0 auto; }
|
|
1668
|
+
.registry-placeholder { margin: 12px 0 0; padding: 11px; border-radius: 8px; color: var(--muted); background: #f7f8f6; font-size: 10px; }
|
|
1669
|
+
.registry-verification { margin-top: 12px; padding: 13px; border: 1px solid; border-radius: 9px; }
|
|
1670
|
+
.registry-verification.verified { color: #315f43; border-color: #c8ddcd; background: #f0f8f2; }
|
|
1671
|
+
.registry-verification.attention { color: #875e1f; border-color: #ead8aa; background: #fff9e9; }
|
|
1672
|
+
.registry-verification > div:first-child strong, .registry-verification > div:first-child span { display: block; }
|
|
1673
|
+
.registry-verification > div:first-child strong { font-size: 11px; }
|
|
1674
|
+
.registry-verification > div:first-child span { margin-top: 3px; font-size: 9px; }
|
|
1675
|
+
.registry-verification ul { margin: 11px 0 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 7px; list-style: none; }
|
|
1676
|
+
.registry-verification li { display: flex; align-items: center; gap: 7px; font-size: 9px; }
|
|
1677
|
+
.registry-verification li span { width: 18px; height: 18px; flex: 0 0 18px; display: grid; place-items: center; border-radius: 50%; color: #2f6845; background: #dceee1; font-size: 8px; font-weight: 900; }
|
|
1678
|
+
.registry-verification li.failed span { color: #9b512e; background: #ffe2cf; }
|
|
1679
|
+
.registry-verification-errors { margin-top: 10px; padding-top: 9px; border-top: 1px solid rgba(74,93,80,.14); }
|
|
1680
|
+
.registry-verification-errors p { margin: 3px 0; font-size: 9px; }
|
|
1681
|
+
.registry-probe-form { margin: 12px 0 10px; display: grid; grid-template-columns: 130px minmax(0,1fr); gap: 9px; }
|
|
1682
|
+
.registry-probe-form label { font-size: 9px; }
|
|
1683
|
+
.registry-probe-body { min-height: 88px; }
|
|
1684
|
+
.registry-count { min-width: 27px; padding: 5px 8px; border-radius: 999px; color: var(--forest); background: var(--mint); font-size: 10px; font-weight: 850; text-align: center; }
|
|
1685
|
+
.registry-dependency-list { margin: 12px 0 0; padding: 0; border: 1px solid var(--line); border-radius: 8px; list-style: none; overflow: hidden; }
|
|
1686
|
+
.registry-dependency-list li { padding: 9px 10px; display: grid; grid-template-columns: 70px minmax(0,1fr) auto; align-items: center; gap: 9px; border-top: 1px solid var(--line); }
|
|
1687
|
+
.registry-dependency-list li:first-child { border-top: 0; }
|
|
1688
|
+
.registry-dependency-list span, .registry-dependency-list small { color: var(--muted); font-size: 8px; }
|
|
1689
|
+
.registry-dependency-list strong { font-size: 9px; }
|
|
1690
|
+
.registry-lifecycle-section { border-color: #e4d7d3; background: #fffcfb; }
|
|
1691
|
+
.registry-lifecycle-actions { margin-top: 12px; display: flex; align-items: center; gap: 9px; }
|
|
1692
|
+
.danger-link { min-height: 38px; padding: 8px 12px; border: 1px solid #e4c6c2; border-radius: 8px; color: #973d36; background: #fff7f5; font-size: 10px; font-weight: 800; }
|
|
1693
|
+
.danger-link:hover { background: #ffefec; }
|
|
1694
|
+
.danger-link:disabled { cursor: not-allowed; opacity: .45; }
|
|
1695
|
+
.registry-blocked-copy { margin: 9px 0 0; color: #82645f; font-size: 9px; }
|
|
1696
|
+
.registry-archive-confirm { margin-top: 12px; padding: 12px; display: flex; align-items: center; justify-content: space-between; gap: 14px; border: 1px solid #e7c6c2; border-radius: 9px; background: #fff1ef; }
|
|
1697
|
+
.registry-archive-confirm strong { font-size: 11px; }
|
|
1698
|
+
.registry-archive-confirm p { margin: 4px 0 0; color: #7e4c47; font-size: 9px; }
|
|
1699
|
+
.registry-archive-confirm > div:last-child { flex: 0 0 auto; display: flex; gap: 7px; }
|
|
1700
|
+
button.danger { min-height: 38px; padding: 9px 13px; border-radius: 8px; color: white; background: #9f4039; font-size: 10px; font-weight: 800; }
|
|
1701
|
+
button.danger:hover { background: #84332e; }
|
|
1702
|
+
|
|
1703
|
+
.registry-wizard { width: min(900px, calc(100% - 28px)); overflow: hidden; }
|
|
1704
|
+
.registry-wizard .detail-head { position: static; }
|
|
1705
|
+
.registry-wizard .detail-head h2 { font-family: var(--sans); font-size: 21px; }
|
|
1706
|
+
.registry-wizard .detail-head p:last-child { font-size: 11px; text-transform: none; }
|
|
1707
|
+
.registry-wizard-steps { margin: 0; padding: 0 22px; display: grid; grid-template-columns: repeat(3,1fr); border-bottom: 1px solid var(--line); background: #fafbf9; list-style: none; }
|
|
1708
|
+
.registry-wizard-steps li { position: relative; padding: 13px 8px; display: flex; align-items: center; gap: 7px; color: #7d8981; border-bottom: 2px solid transparent; font-size: 10px; font-weight: 750; }
|
|
1709
|
+
.registry-wizard-steps li.current { color: var(--forest-dark); border-bottom-color: var(--forest); }
|
|
1710
|
+
.registry-wizard-steps li.complete { color: #47715a; }
|
|
1711
|
+
.registry-wizard-steps li span { width: 22px; height: 22px; display: grid; place-items: center; border-radius: 50%; color: inherit; background: #edf1ed; font-size: 9px; }
|
|
1712
|
+
.registry-wizard-steps li.current span { color: white; background: var(--forest); }
|
|
1713
|
+
.registry-wizard .editor-form { padding: 0; gap: 0; }
|
|
1714
|
+
.registry-wizard-content { min-height: 470px; max-height: calc(100vh - 255px); padding: 22px; overflow-y: auto; }
|
|
1715
|
+
.registry-step-heading { margin-bottom: 16px; }
|
|
1716
|
+
.registry-step-heading h3 { margin: 0; font-size: 17px; }
|
|
1717
|
+
.registry-step-heading p { margin: 5px 0 0; color: var(--muted); font-size: 11px; line-height: 1.5; }
|
|
1718
|
+
.registry-purpose-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
|
|
1719
|
+
.registry-purpose-grid > button { padding: 14px; display: grid; grid-template-columns: 38px minmax(0,1fr); gap: 11px; border: 1px solid var(--line); border-radius: 10px; color: var(--ink); background: white; text-align: left; }
|
|
1720
|
+
.registry-purpose-grid > button:hover { border-color: #9db4a5; background: #fbfdfb; }
|
|
1721
|
+
.registry-purpose-grid > button.selected { border-color: #6e9f80; background: #f0f7f2; box-shadow: 0 0 0 2px rgba(63,127,87,.1); }
|
|
1722
|
+
.registry-purpose-grid i { width: 38px; height: 38px; display: grid; place-items: center; border-radius: 9px; color: var(--forest); background: var(--mint); font-style: normal; font-size: 16px; }
|
|
1723
|
+
.registry-purpose-grid strong, .registry-purpose-grid small, .registry-purpose-grid em { display: block; }
|
|
1724
|
+
.registry-purpose-grid strong { font-size: 12px; }
|
|
1725
|
+
.registry-purpose-grid small { margin-top: 4px; color: var(--muted); font-size: 10px; line-height: 1.4; }
|
|
1726
|
+
.registry-purpose-grid em { width: fit-content; margin-top: 8px; padding: 3px 6px; border-radius: 5px; color: #4f6256; background: #eef2ee; font-size: 8px; font-style: normal; font-weight: 750; }
|
|
1727
|
+
.registry-identity-fields { margin-top: 16px; }
|
|
1728
|
+
.registry-form label { font-size: 10px; }
|
|
1729
|
+
.registry-form label > small { margin-top: 5px; display: block; color: var(--muted); font-size: 9px; font-weight: 500; line-height: 1.4; }
|
|
1730
|
+
.registry-form input, .registry-form select, .registry-form textarea:not(.code-input) { margin-top: 5px; font-size: 11px; }
|
|
1731
|
+
.registry-action-boundary { color: #7e4740; border-color: #e6cbc7; background: #fff3f1; }
|
|
1732
|
+
.registry-action-boundary strong { color: #8b3731; }
|
|
1733
|
+
.registry-advanced { margin-top: 14px; padding: 11px 13px; border: 1px solid var(--line); border-radius: 9px; background: #fafbf9; }
|
|
1734
|
+
.registry-advanced summary { color: #41544a; cursor: pointer; font-size: 10px; font-weight: 800; }
|
|
1735
|
+
.registry-advanced > p { margin: 8px 0 12px; color: var(--muted); font-size: 9px; }
|
|
1736
|
+
.registry-review { display: grid; gap: 14px; }
|
|
1737
|
+
.registry-review dl { margin: 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
|
|
1738
|
+
.registry-review dl > div { padding: 12px 14px; display: grid; grid-template-columns: 150px minmax(0,1fr); gap: 12px; border-top: 1px solid var(--line); }
|
|
1739
|
+
.registry-review dl > div:first-child { border-top: 0; }
|
|
1740
|
+
.registry-review dt { color: var(--muted); font-size: 9px; }
|
|
1741
|
+
.registry-review dd { margin: 0; overflow-wrap: anywhere; font-size: 10px; font-weight: 750; text-transform: capitalize; }
|
|
1742
|
+
.registry-review-note { padding: 14px; border-radius: 10px; color: #315640; background: #eff7f1; }
|
|
1743
|
+
.registry-review-note strong { font-size: 11px; }
|
|
1744
|
+
.registry-review-note p { margin: 5px 0 0; font-size: 10px; line-height: 1.55; }
|
|
1745
|
+
.registry-secret-next { padding: 14px; border: 1px solid #d9d4e8; border-radius: 10px; color: #4f4969; background: #f7f5fc; }
|
|
1746
|
+
.registry-secret-next strong { font-size: 11px; }
|
|
1747
|
+
.registry-secret-next p { margin: 5px 0 9px; font-size: 10px; line-height: 1.5; }
|
|
1748
|
+
.registry-secret-next code { padding: 8px 10px; display: block; overflow-x: auto; border-radius: 7px; color: #35304c; background: white; font: 9px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
1749
|
+
.registry-wizard-footer { min-height: 70px; padding: 13px 22px; }
|
|
1750
|
+
.registry-wizard-footer .error { max-width: 500px; }
|
|
1751
|
+
|
|
1752
|
+
@media (max-width: 980px) {
|
|
1753
|
+
.registry-model { grid-template-columns: 1fr 1fr; }
|
|
1754
|
+
.registry-model > b { display: none; }
|
|
1755
|
+
.registry-toolbar { align-items: stretch; flex-wrap: wrap; }
|
|
1756
|
+
.registry-toolbar .search-field { flex-basis: 100%; }
|
|
1757
|
+
.registry-resource-row { grid-template-columns: 42px minmax(0,1fr) 90px auto; }
|
|
1758
|
+
.registry-resource-relationship { display: none; }
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
@media (max-width: 700px) {
|
|
1762
|
+
.registry-model { grid-template-columns: 1fr; }
|
|
1763
|
+
.registry-tabs-row { align-items: flex-start; flex-direction: column; }
|
|
1764
|
+
.registry-filter-chips { width: 100%; overflow-x: auto; }
|
|
1765
|
+
.registry-archived-toggle { margin-left: 0; }
|
|
1766
|
+
.registry-resource-row { grid-template-columns: 38px minmax(0,1fr); align-items: start; }
|
|
1767
|
+
.registry-resource-state { grid-column: 2; }
|
|
1768
|
+
.registry-resource-row > button { width: 100%; grid-column: 2; justify-self: stretch; }
|
|
1769
|
+
.registry-detail-summary { grid-template-columns: 1fr 1fr; }
|
|
1770
|
+
.registry-section-heading { flex-direction: column; }
|
|
1771
|
+
.registry-section-heading button { width: 100%; }
|
|
1772
|
+
.registry-verification ul { grid-template-columns: 1fr; }
|
|
1773
|
+
.registry-archive-confirm { align-items: stretch; flex-direction: column; }
|
|
1774
|
+
.registry-purpose-grid { grid-template-columns: 1fr; }
|
|
1775
|
+
.registry-wizard-content { min-height: 0; }
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
@media (max-width: 460px) {
|
|
1779
|
+
.registry-detail-dialog, .registry-wizard { width: 100%; max-height: 100vh; margin: 0; border: 0; border-radius: 0; }
|
|
1780
|
+
.registry-wizard-steps { padding-inline: 10px; }
|
|
1781
|
+
.registry-wizard-steps li { padding-inline: 4px; font-size: 9px; }
|
|
1782
|
+
.registry-wizard-content, .registry-detail-body { padding: 14px; }
|
|
1783
|
+
.registry-detail-summary { grid-template-columns: 1fr; }
|
|
1784
|
+
.registry-probe-form, .registry-review dl > div { grid-template-columns: 1fr; }
|
|
1785
|
+
.registry-lifecycle-actions, .registry-archive-confirm > div:last-child { align-items: stretch; flex-direction: column; }
|
|
1786
|
+
.registry-lifecycle-actions button, .registry-archive-confirm button { width: 100%; }
|
|
1787
|
+
.registry-form .form-grid { grid-template-columns: 1fr; }
|
|
1788
|
+
.registry-form .wide-field { grid-column: 1; }
|
|
1789
|
+
.registry-wizard-footer { align-items: stretch; flex-direction: column; }
|
|
1790
|
+
.registry-wizard-footer > div { width: 100%; display: grid; grid-template-columns: 1fr 1fr; }
|
|
1791
|
+
.registry-wizard-footer button { width: 100%; }
|
|
1792
|
+
}
|
|
@@ -281,10 +281,12 @@ export async function loadWorkflowExecutionContext(env: Env, runId: string, bran
|
|
|
281
281
|
WHERE wr.id = ?1 LIMIT 1
|
|
282
282
|
`).bind(runId).first<RunContextRow>();
|
|
283
283
|
if (!row) throw new ComponentExecutionError("component_report_context_missing", false);
|
|
284
|
-
const [targetResult, answersResult, stepsResult, findingsResult] = await Promise.all([
|
|
284
|
+
const [targetResult, reporterResult, answersResult, stepsResult, findingsResult] = await Promise.all([
|
|
285
285
|
env.DB.prepare(`SELECT target_type, target_reference, owner_reference, customer_url, signed_facts_json FROM report_targets WHERE report_id = ?1 ORDER BY created_at, id LIMIT 50`).bind(row.reportId).all<{
|
|
286
286
|
target_type: string; target_reference: string; owner_reference: string | null; customer_url: string | null; signed_facts_json: string;
|
|
287
287
|
}>(),
|
|
288
|
+
env.DB.prepare(`SELECT reference FROM report_participants WHERE report_id = ?1 AND audience = 'reporter' ORDER BY created_at, id LIMIT 1`)
|
|
289
|
+
.bind(row.reportId).first<{ reference: string | null }>(),
|
|
288
290
|
env.DB.prepare(`SELECT field_key, value_json FROM report_answers WHERE report_id = ?1 ORDER BY field_key LIMIT 200`).bind(row.reportId).all<{ field_key: string; value_json: string }>(),
|
|
289
291
|
env.DB.prepare(`
|
|
290
292
|
SELECT s.node_id, o.inline_json FROM workflow_step_runs s
|
|
@@ -327,6 +329,7 @@ export async function loadWorkflowExecutionContext(env: Env, runId: string, bran
|
|
|
327
329
|
routing_rule_version_id: row.routingRuleVersionId,
|
|
328
330
|
},
|
|
329
331
|
queue: { id: row.queueId, version_id: row.queueVersionId },
|
|
332
|
+
reporter: { reference: reporterResult?.reference ?? null },
|
|
330
333
|
target: targets[0] ?? null,
|
|
331
334
|
targets,
|
|
332
335
|
answers,
|
|
@@ -94,7 +94,9 @@ export async function listConnections(db: D1Database, includeArchived = false):
|
|
|
94
94
|
v.credential_strategy AS credentialStrategy, v.published_at AS publishedAt,
|
|
95
95
|
h.state AS healthState, h.last_success_at AS lastSuccessAt,
|
|
96
96
|
h.last_error_code AS lastErrorCode,
|
|
97
|
-
(SELECT COUNT(*) FROM component_versions cv
|
|
97
|
+
(SELECT COUNT(*) FROM component_versions cv
|
|
98
|
+
JOIN component_definitions cd ON cd.id = cv.definition_id
|
|
99
|
+
WHERE cv.connection_version_id = v.id AND cv.retired_at IS NULL AND cd.status != 'archived') AS dependencyCount
|
|
98
100
|
FROM connection_definitions d
|
|
99
101
|
LEFT JOIN connection_versions v ON v.id = d.active_version_id
|
|
100
102
|
LEFT JOIN connection_health h ON h.connection_version_id = v.id
|
|
@@ -178,8 +180,10 @@ export async function updateConnectionDefinition(
|
|
|
178
180
|
}
|
|
179
181
|
if (status === "archived") {
|
|
180
182
|
const dependency = await db.prepare(`
|
|
181
|
-
SELECT COUNT(*) AS count FROM component_versions cv
|
|
182
|
-
|
|
183
|
+
SELECT COUNT(*) AS count FROM component_versions cv
|
|
184
|
+
JOIN component_definitions d ON d.id = cv.definition_id
|
|
185
|
+
JOIN connection_versions v ON v.id = cv.connection_version_id
|
|
186
|
+
WHERE v.connection_id = ?1 AND cv.retired_at IS NULL AND d.status != 'archived'
|
|
183
187
|
`).bind(connectionId).first<{ count: number }>();
|
|
184
188
|
if (Number(dependency?.count ?? 0) > 0) throw new ApiError(409, "connection_has_dependencies", "Retire dependent component versions before archiving this connection.");
|
|
185
189
|
}
|
|
@@ -312,7 +316,7 @@ export async function connectionDependencies(db: D1Database, connectionId: strin
|
|
|
312
316
|
SELECT d.id AS componentId, d.name, v.id AS componentVersionId, v.version
|
|
313
317
|
FROM component_versions v JOIN component_definitions d ON d.id = v.definition_id
|
|
314
318
|
JOIN connection_versions cv ON cv.id = v.connection_version_id
|
|
315
|
-
WHERE cv.connection_id = ?1 AND v.retired_at IS NULL ORDER BY d.name, v.version DESC
|
|
319
|
+
WHERE cv.connection_id = ?1 AND v.retired_at IS NULL AND d.status != 'archived' ORDER BY d.name, v.version DESC
|
|
316
320
|
`).bind(connectionId).all<Record<string, unknown>>(),
|
|
317
321
|
db.prepare(`
|
|
318
322
|
SELECT DISTINCT d.id AS workflowId, d.name, wv.id AS workflowVersionId, wv.version
|
|
@@ -320,7 +324,7 @@ export async function connectionDependencies(db: D1Database, connectionId: strin
|
|
|
320
324
|
JOIN workflow_versions wv ON wv.id = dep.workflow_version_id
|
|
321
325
|
JOIN workflow_definitions d ON d.id = wv.workflow_id
|
|
322
326
|
JOIN connection_versions cv ON cv.id = dep.dependency_version_id
|
|
323
|
-
WHERE dep.dependency_type = 'connection' AND cv.connection_id = ?1
|
|
327
|
+
WHERE dep.dependency_type = 'connection' AND cv.connection_id = ?1 AND wv.retired_at IS NULL
|
|
324
328
|
ORDER BY d.name, wv.version DESC
|
|
325
329
|
`).bind(connectionId).all<Record<string, unknown>>(),
|
|
326
330
|
db.prepare(`
|
|
@@ -538,8 +542,10 @@ export async function updateComponentDefinition(
|
|
|
538
542
|
}
|
|
539
543
|
if (status === "archived") {
|
|
540
544
|
const dependency = await db.prepare(`
|
|
541
|
-
SELECT COUNT(*) AS count FROM workflow_dependencies dep
|
|
542
|
-
|
|
545
|
+
SELECT COUNT(*) AS count FROM workflow_dependencies dep
|
|
546
|
+
JOIN workflow_versions wv ON wv.id = dep.workflow_version_id
|
|
547
|
+
JOIN component_versions v ON v.id = dep.dependency_version_id
|
|
548
|
+
WHERE dep.dependency_type = 'component' AND v.definition_id = ?1 AND wv.retired_at IS NULL
|
|
543
549
|
`).bind(componentId).first<{ count: number }>();
|
|
544
550
|
if (Number(dependency?.count ?? 0) > 0) throw new ApiError(409, "component_has_dependencies", "Retire dependent workflow versions before archiving this component.");
|
|
545
551
|
}
|
|
@@ -674,7 +680,7 @@ export async function componentDependencies(db: D1Database, componentId: string)
|
|
|
674
680
|
JOIN workflow_versions wv ON wv.id = dep.workflow_version_id
|
|
675
681
|
JOIN workflow_definitions d ON d.id = wv.workflow_id
|
|
676
682
|
JOIN component_versions cv ON cv.id = dep.dependency_version_id
|
|
677
|
-
WHERE dep.dependency_type = 'component' AND cv.definition_id = ?1
|
|
683
|
+
WHERE dep.dependency_type = 'component' AND cv.definition_id = ?1 AND wv.retired_at IS NULL
|
|
678
684
|
ORDER BY d.name, wv.version DESC
|
|
679
685
|
`).bind(componentId).all<Record<string, unknown>>();
|
|
680
686
|
return { workflows: workflows.results };
|
|
@@ -685,10 +691,13 @@ export async function validateComponentDefinition(db: D1Database, componentId: s
|
|
|
685
691
|
SELECT d.status, d.effect_class AS effectClass, d.active_version_id AS activeVersionId,
|
|
686
692
|
v.implementation_kind AS implementationKind, v.implementation_reference AS implementationReference,
|
|
687
693
|
v.input_schema_json AS inputSchemaJson, v.output_schema_json AS outputSchemaJson,
|
|
688
|
-
v.connection_version_id AS connectionVersionId,
|
|
694
|
+
v.connection_version_id AS connectionVersionId, cd.status AS connectionStatus,
|
|
695
|
+
cv.retired_at AS connectionRetiredAt,
|
|
689
696
|
(SELECT COUNT(*) FROM component_fixtures f WHERE f.component_version_id = v.id) AS fixtureCount,
|
|
690
697
|
(SELECT COUNT(*) FROM component_test_runs t WHERE t.component_version_id = v.id AND t.status = 'passed') AS passingTestCount
|
|
691
698
|
FROM component_definitions d LEFT JOIN component_versions v ON v.id = d.active_version_id
|
|
699
|
+
LEFT JOIN connection_versions cv ON cv.id = v.connection_version_id
|
|
700
|
+
LEFT JOIN connection_definitions cd ON cd.id = cv.connection_id
|
|
692
701
|
WHERE d.id = ?1 LIMIT 1
|
|
693
702
|
`).bind(componentId).first<Record<string, unknown>>();
|
|
694
703
|
if (!row) throw new ApiError(404, "component_not_found", "The component does not exist.");
|
|
@@ -697,6 +706,7 @@ export async function validateComponentDefinition(db: D1Database, componentId: s
|
|
|
697
706
|
if (!parsedJson(String(row.inputSchemaJson ?? ""), null)) errors.push("input_schema_invalid");
|
|
698
707
|
if (!parsedJson(String(row.outputSchemaJson ?? ""), null)) errors.push("output_schema_invalid");
|
|
699
708
|
if (["webhook", "external_api"].includes(String(row.implementationKind)) && !row.connectionVersionId) errors.push("connection_version_missing");
|
|
709
|
+
if (row.connectionVersionId && (row.connectionStatus !== "active" || row.connectionRetiredAt)) errors.push("connection_version_unavailable");
|
|
700
710
|
if (Number(row.fixtureCount ?? 0) < 1) errors.push("fixture_missing");
|
|
701
711
|
if (Number(row.passingTestCount ?? 0) < 1) errors.push("passing_test_missing");
|
|
702
712
|
return { valid: errors.length === 0, errors, ...row };
|
|
@@ -101,7 +101,8 @@ export async function receiveReporterEmail(
|
|
|
101
101
|
return { reportId: null, stored: false };
|
|
102
102
|
}
|
|
103
103
|
const report = await env.DB.prepare(`
|
|
104
|
-
SELECT r.id, r.state, p.id AS participantId, p.contact_ciphertext AS contactCiphertext
|
|
104
|
+
SELECT r.id, r.state, p.id AS participantId, p.contact_ciphertext AS contactCiphertext,
|
|
105
|
+
p.audience AS audience
|
|
105
106
|
FROM reports r
|
|
106
107
|
JOIN report_participants p ON p.report_id = r.id AND p.audience = ?2
|
|
107
108
|
WHERE r.public_reference = ?1
|
|
@@ -111,9 +112,15 @@ export async function receiveReporterEmail(
|
|
|
111
112
|
await recordUnmatchedEmail(env.DB, message, "report_or_reporter_contact_missing");
|
|
112
113
|
return { reportId: null, stored: false };
|
|
113
114
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
let expectedSender: string;
|
|
116
|
+
try {
|
|
117
|
+
expectedSender = (await decryptReportContact(
|
|
118
|
+
env.CREDENTIAL_ENCRYPTION_KEY, report.contactCiphertext, report.id, report.audience,
|
|
119
|
+
)).trim().toLowerCase();
|
|
120
|
+
} catch {
|
|
121
|
+
await recordUnmatchedEmail(env.DB, message, "reporter_contact_unreadable");
|
|
122
|
+
return { reportId: report.id, stored: false };
|
|
123
|
+
}
|
|
117
124
|
if (!await secretsEqual(message.from.trim().toLowerCase(), expectedSender)) {
|
|
118
125
|
await recordUnmatchedEmail(env.DB, message, "sender_mismatch");
|
|
119
126
|
return { reportId: null, stored: false };
|
|
@@ -67,7 +67,8 @@ export function reporterEmailConfigured(env: unknown): boolean {
|
|
|
67
67
|
export type CaseReplyAudience = "reporter" | "affected_user";
|
|
68
68
|
|
|
69
69
|
const legacyReplyTokenPattern = /^r1\.([a-z0-9_-]{16})\.([0-9a-f]{8})\.([0-9a-f]{24})$/u;
|
|
70
|
-
const replyTokenPattern = /^
|
|
70
|
+
const replyTokenPattern = /^r([ra])\.([a-z0-9_-]{16})\.([0-9a-f]{8})\.([0-9a-f]{24})$/u;
|
|
71
|
+
const transitionalReplyTokenPattern = /^r2([ra])\.([a-z0-9_-]{16})\.([0-9a-f]{8})\.([0-9a-f]{24})$/u;
|
|
71
72
|
const encoder = new TextEncoder();
|
|
72
73
|
|
|
73
74
|
function hex(bytes: Uint8Array): string {
|
|
@@ -98,7 +99,7 @@ export async function reporterReplyToken(input: {
|
|
|
98
99
|
const expiry = input.expiresAt.toString(16).padStart(8, "0");
|
|
99
100
|
if (!/^[0-9a-f]{8}$/u.test(expiry)) throw new Error("The reporter reply expiry is outside the supported range.");
|
|
100
101
|
const audience = input.audience === "affected_user" ? "a" : "r";
|
|
101
|
-
const payload = `
|
|
102
|
+
const payload = `r${audience}.${reference}.${expiry}`;
|
|
102
103
|
return `${payload}.${hex(await replySignature(input.signingSecret, payload))}`;
|
|
103
104
|
}
|
|
104
105
|
|
|
@@ -108,21 +109,23 @@ export async function verifyReporterReplyToken(
|
|
|
108
109
|
currentTime = Math.floor(Date.now() / 1_000),
|
|
109
110
|
): Promise<{ publicReference: string; expiresAt: number; audience: CaseReplyAudience } | null> {
|
|
110
111
|
const current = replyTokenPattern.exec(token);
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const
|
|
112
|
+
const transitional = current ? null : transitionalReplyTokenPattern.exec(token);
|
|
113
|
+
const legacy = current || transitional ? null : legacyReplyTokenPattern.exec(token);
|
|
114
|
+
if (!current && !transitional && !legacy) return null;
|
|
115
|
+
const reference = current?.[2] ?? transitional?.[2] ?? legacy?.[1] ?? "";
|
|
116
|
+
const expiryHex = current?.[3] ?? transitional?.[3] ?? legacy?.[2] ?? "";
|
|
117
|
+
const signatureHex = current?.[4] ?? transitional?.[4] ?? legacy?.[3] ?? "";
|
|
116
118
|
const expiry = Number.parseInt(expiryHex, 16);
|
|
117
119
|
const supplied = Uint8Array.from(signatureHex.match(/.{2}/gu) ?? [], (pair) => Number.parseInt(pair, 16));
|
|
118
120
|
if (!expiry || expiry <= currentTime) return null;
|
|
119
|
-
const payload = current ? `
|
|
121
|
+
const payload = current ? `r${current[1]}.${reference}.${expiryHex}`
|
|
122
|
+
: transitional ? `r2${transitional[1]}.${reference}.${expiryHex}` : `r1.${reference}.${expiryHex}`;
|
|
120
123
|
const expected = await replySignature(signingSecret, payload);
|
|
121
124
|
if (!constantTimeEqual(supplied, expected)) return null;
|
|
122
125
|
return {
|
|
123
126
|
publicReference: `RPT-${reference.toUpperCase()}`,
|
|
124
127
|
expiresAt: expiry,
|
|
125
|
-
audience: current?.[1] === "a" ? "affected_user" : "reporter",
|
|
128
|
+
audience: (current?.[1] ?? transitional?.[1]) === "a" ? "affected_user" : "reporter",
|
|
126
129
|
};
|
|
127
130
|
}
|
|
128
131
|
|
|
@@ -79,6 +79,7 @@ interface RoutingJobRow {
|
|
|
79
79
|
customer_url: string | null;
|
|
80
80
|
public_reference: string;
|
|
81
81
|
installation_id: string;
|
|
82
|
+
reporter_reference: string | null;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
interface QueueRoutingRow {
|
|
@@ -649,7 +650,10 @@ export async function processRoutingJob(
|
|
|
649
650
|
SELECT j.id, j.report_id, j.state, r.reason_code, r.source, r.form_version_id,
|
|
650
651
|
r.installation_id, r.public_reference,
|
|
651
652
|
t.target_type, t.target_reference, t.owner_reference, t.customer_url,
|
|
652
|
-
t.routing_facts_json AS trusted_facts_json, r.state AS report_state
|
|
653
|
+
t.routing_facts_json AS trusted_facts_json, r.state AS report_state,
|
|
654
|
+
(SELECT p.reference FROM report_participants p
|
|
655
|
+
WHERE p.report_id = r.id AND p.audience = 'reporter'
|
|
656
|
+
ORDER BY p.created_at, p.id LIMIT 1) AS reporter_reference
|
|
653
657
|
FROM background_jobs j
|
|
654
658
|
JOIN reports r ON r.id = j.report_id
|
|
655
659
|
JOIN report_targets t ON t.report_id = r.id
|
|
@@ -716,6 +720,7 @@ export async function processRoutingJob(
|
|
|
716
720
|
id: job.report_id, public_reference: job.public_reference, reason_code: job.reason_code,
|
|
717
721
|
source: job.source, target_type: job.target_type, priority: 50,
|
|
718
722
|
},
|
|
723
|
+
reporter: { reference: job.reporter_reference },
|
|
719
724
|
target: {
|
|
720
725
|
type: job.target_type, reference: job.target_reference,
|
|
721
726
|
owner_reference: job.owner_reference, customer_url: job.customer_url,
|