portal-mojo 0.2.19 → 0.2.21

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.
Files changed (157) hide show
  1. package/docs/README.md +7 -1
  2. package/docs/admin-credentials.md +27 -0
  3. package/docs/admin-deployments.md +201 -0
  4. package/docs/admin-dns-certificates.md +160 -0
  5. package/docs/admin-dns-domains.md +92 -0
  6. package/docs/admin-dns.md +18 -0
  7. package/docs/admin-edge.md +200 -196
  8. package/docs/admin-fleet-configuration.md +98 -2
  9. package/docs/admin-geolocation.md +100 -0
  10. package/docs/admin-identity-directories.md +12 -0
  11. package/docs/admin-maintenance.md +133 -0
  12. package/docs/admin-modals.md +45 -2
  13. package/docs/admin-operational-fleet.md +206 -0
  14. package/docs/admin-presentation.md +51 -0
  15. package/docs/admin-signin.md +12 -2
  16. package/docs/auth-pages.md +18 -6
  17. package/docs/auth.md +16 -2
  18. package/docs/client.md +7 -0
  19. package/docs/collection-select.md +14 -0
  20. package/docs/combobox.md +18 -0
  21. package/docs/popover.md +5 -4
  22. package/package.json +1 -1
  23. package/src/admin/core/OperationsHelp.tsx +10 -0
  24. package/src/admin/core/index.ts +6 -4
  25. package/src/admin/core/route-context.ts +6 -0
  26. package/src/admin/credentials/AcmeConsumerKeyDialog.tsx +88 -0
  27. package/src/admin/credentials/acme-consumer-key.ts +34 -0
  28. package/src/admin/credentials/group-api-keys.tsx +5 -3
  29. package/src/admin/deployments/ApiDeploymentDetail.tsx +58 -0
  30. package/src/admin/deployments/DeploymentsPage.tsx +73 -0
  31. package/src/admin/deployments/WebAppDeploySetup.tsx +49 -0
  32. package/src/admin/deployments/WebAppDeploymentDetail.tsx +59 -0
  33. package/src/admin/deployments/WebAppDetail.tsx +104 -0
  34. package/src/admin/deployments/WebAppEditDialog.tsx +70 -0
  35. package/src/admin/deployments/WebAppKeyDialog.tsx +25 -0
  36. package/src/admin/deployments/WebAppOnboardingDialog.tsx +217 -0
  37. package/src/admin/deployments/WebAppRollbackDialog.tsx +82 -0
  38. package/src/admin/deployments/WebAppUploadPanel.tsx +132 -0
  39. package/src/admin/deployments/api.ts +158 -0
  40. package/src/admin/deployments/authority-state.ts +9 -0
  41. package/src/admin/deployments/deployment-state.ts +13 -0
  42. package/src/admin/deployments/index.ts +4 -0
  43. package/src/admin/deployments/onboarding-state.ts +11 -0
  44. package/src/admin/deployments/permissions.ts +37 -0
  45. package/src/admin/deployments/receipts.ts +78 -0
  46. package/src/admin/deployments/sanitize.ts +73 -0
  47. package/src/admin/deployments/shared.tsx +39 -0
  48. package/src/admin/deployments/types.ts +238 -0
  49. package/src/admin/deployments/upload.ts +53 -0
  50. package/src/admin/deployments/useDeploymentObservation.ts +104 -0
  51. package/src/admin/dns/CertificateDetail.tsx +8 -3
  52. package/src/admin/dns/CertificateRequestDialog.tsx +45 -7
  53. package/src/admin/dns/DomainDetail.tsx +14 -4
  54. package/src/admin/dns/DomainOnboardingContent.tsx +44 -0
  55. package/src/admin/dns/DomainOnboardingDialog.tsx +5 -24
  56. package/src/admin/dns/DomainPurchaseWizard.tsx +1 -1
  57. package/src/admin/dns/DomainRegisterExisting.tsx +63 -0
  58. package/src/admin/dns/DomainRegistrarSettings.tsx +52 -0
  59. package/src/admin/dns/DomainsPage.tsx +26 -13
  60. package/src/admin/dns/SharedValidationDialog.tsx +298 -0
  61. package/src/admin/dns/SharedValidationPanel.tsx +76 -0
  62. package/src/admin/dns/api.ts +4 -2
  63. package/src/admin/dns/domain-navigation.ts +16 -0
  64. package/src/admin/dns/registrar-api.ts +13 -0
  65. package/src/admin/dns/shared-validation-api.ts +90 -0
  66. package/src/admin/dns/shared-validation-data.ts +106 -0
  67. package/src/admin/domains/assistant.ts +5 -4
  68. package/src/admin/domains/communications.ts +10 -9
  69. package/src/admin/domains/identity.ts +8 -7
  70. package/src/admin/domains/infrastructure.ts +19 -13
  71. package/src/admin/domains/lazy-page.ts +6 -0
  72. package/src/admin/domains/observability.ts +6 -5
  73. package/src/admin/domains/operations.ts +27 -10
  74. package/src/admin/domains/security.ts +16 -15
  75. package/src/admin/edge/EdgeHelp.tsx +10 -0
  76. package/src/admin/edge/ServingReadiness.tsx +54 -0
  77. package/src/admin/edge/VhostDetail.tsx +145 -139
  78. package/src/admin/edge/VhostWizard.tsx +204 -115
  79. package/src/admin/edge/VhostsPage.tsx +27 -28
  80. package/src/admin/edge/api.ts +24 -10
  81. package/src/admin/edge/models.ts +4 -4
  82. package/src/admin/edge/serving-data.ts +78 -0
  83. package/src/admin/fleet/CapacityEditor.tsx +81 -0
  84. package/src/admin/fleet/CapacityInspector.tsx +19 -0
  85. package/src/admin/fleet/CapacityProgress.tsx +9 -0
  86. package/src/admin/fleet/FleetHelp.tsx +7 -0
  87. package/src/admin/fleet/FleetOverview.tsx +150 -0
  88. package/src/admin/fleet/FleetPage.tsx +11 -0
  89. package/src/admin/fleet/FleetWorkspace.tsx +82 -0
  90. package/src/admin/fleet/ScaleFleetDialog.tsx +169 -0
  91. package/src/admin/fleet/access.ts +22 -0
  92. package/src/admin/fleet/api.ts +77 -0
  93. package/src/admin/fleet/data.ts +148 -0
  94. package/src/admin/fleet/index.ts +8 -0
  95. package/src/admin/fleet/operations.ts +179 -0
  96. package/src/admin/fleet/standalone-data.ts +87 -0
  97. package/src/admin/fleet/types.ts +208 -0
  98. package/src/admin/fleet-configuration/FleetConfigurationPage.tsx +14 -8
  99. package/src/admin/fleet-configuration/SharedValidationConnectionPanel.tsx +166 -0
  100. package/src/admin/fleet-configuration/data.ts +4 -0
  101. package/src/admin/fleet-configuration/operator-destinations.ts +20 -0
  102. package/src/admin/fleet-configuration/shared-validation-data.ts +143 -0
  103. package/src/admin/geolocation/GeoLocationPage.tsx +171 -0
  104. package/src/admin/geolocation/data.ts +138 -0
  105. package/src/admin/geolocation/index.ts +3 -0
  106. package/src/admin/identity/groups/group-sections/SubGroupsSection.tsx +73 -2
  107. package/src/admin/identity/groups/models.ts +2 -0
  108. package/src/admin/identity/members/GroupMembersPanel.tsx +2 -8
  109. package/src/admin/index.ts +8 -0
  110. package/src/admin/maintenance/MaintenanceDialogs.tsx +161 -0
  111. package/src/admin/maintenance/MaintenancePage.tsx +442 -0
  112. package/src/admin/maintenance/data.ts +486 -0
  113. package/src/admin/maintenance/index.ts +3 -0
  114. package/src/admin/maintenance/operations.ts +208 -0
  115. package/src/admin/public/infrastructure.ts +1 -0
  116. package/src/admin/public/operations.ts +3 -0
  117. package/src/admin/registry.ts +6 -2
  118. package/src/admin/settings/SettingDetail.tsx +7 -2
  119. package/src/admin/settings/SettingEditor.tsx +2 -1
  120. package/src/admin/settings/SettingsPage.tsx +3 -1
  121. package/src/admin/settings/model.ts +1 -0
  122. package/src/admin/signin/SigninPage.tsx +17 -3
  123. package/src/admin/signin/api.ts +2 -0
  124. package/src/charts/MetricsChartView.tsx +4 -11
  125. package/src/charts/MetricsMiniWidget.tsx +3 -15
  126. package/src/client/auth.ts +77 -25
  127. package/src/client/client.ts +6 -0
  128. package/src/client/deployments-mock.ts +375 -0
  129. package/src/client/domain-operations-mock.ts +37 -0
  130. package/src/client/fleet-mock.ts +73 -1
  131. package/src/client/geo-provider-mock.ts +82 -0
  132. package/src/client/index.ts +2 -2
  133. package/src/client/maintenance-mock.ts +285 -0
  134. package/src/client/mock.ts +280 -11
  135. package/src/client/operational-fleet-mock.ts +279 -0
  136. package/src/client/operations-mock.ts +85 -0
  137. package/src/client/serving-mock.ts +22 -0
  138. package/src/styles/components/admin-deployments.css +35 -0
  139. package/src/styles/components/admin-dns-certificates.css +33 -0
  140. package/src/styles/components/admin-domains.css +12 -0
  141. package/src/styles/components/admin-edge.css +125 -1
  142. package/src/styles/components/admin-geolocation.css +40 -0
  143. package/src/styles/components/admin-maintenance.css +127 -0
  144. package/src/styles/components/admin-operational-fleet.css +38 -0
  145. package/src/styles/components/admin-operations.css +9 -0
  146. package/src/styles/components/admin-signin.css +2 -0
  147. package/src/styles/components/combobox.css +12 -2
  148. package/src/styles/components/fleet-page.css +9 -0
  149. package/src/styles/index.css +7 -0
  150. package/src/ui/CollectionSelect.tsx +30 -8
  151. package/src/ui/ComboBox.tsx +39 -8
  152. package/src/ui/FilterBar.tsx +3 -5
  153. package/src/ui/ModelTable.tsx +2 -8
  154. package/src/ui/Popover.tsx +15 -7
  155. package/src/ui/date/TimePicker.tsx +4 -14
  156. package/src/ui/image-editor/ImageEditor.tsx +4 -5
  157. package/src/ui/schema-form-core.tsx +3 -5
@@ -1 +1,125 @@
1
- .edge-kind-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.edge-kind-card{position:relative;display:grid;gap:6px;padding:14px;border:1px solid var(--line);border-radius:10px;background:var(--surface);cursor:pointer}.edge-kind-card:hover{border-color:var(--accent)}.edge-kind-card.is-selected{border-color:var(--accent);background:var(--accent-soft)}.edge-kind-card:focus-within{outline:2px solid var(--accent);outline-offset:2px}.edge-kind-card input{position:absolute;opacity:0;pointer-events:none}.edge-kind-card i{font-size:22px;color:var(--accent)}.edge-kind-card span{color:var(--mute);font-size:13px}.edge-form-grid{display:grid;gap:14px}.edge-server-name{display:grid;gap:3px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface2)}.edge-server-name span,.edge-review dt{font-size:11px;color:var(--mute);text-transform:uppercase;letter-spacing:.06em}.edge-server-name code{font-size:15px;overflow-wrap:anywhere}.edge-path-list,.edge-route-list{display:grid;gap:8px}.edge-path-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:center}.edge-route-row{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.3fr) auto;gap:8px;align-items:center}.edge-warning{display:flex;gap:8px;align-items:baseline;padding:10px 12px;border:1px solid var(--warn);border-radius:8px;color:var(--ink);background:var(--surface2)}.edge-warning i{color:var(--warn)}.edge-note{display:grid;gap:3px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface2)}.edge-note span{color:var(--mute);font-size:13px}.edge-review{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:8px 16px;margin:0}.edge-review dd{margin:0;overflow-wrap:anywhere}.edge-routes{display:grid;gap:10px}.edge-route-item{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr) auto;gap:10px;align-items:center;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--surface)}.edge-route-actions,.edge-row-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}.edge-route-editor{display:grid;gap:12px;padding:14px;border:1px solid var(--line);border-radius:10px;background:var(--surface2)}.edge-inline-code{margin-right:6px}.edge-blocklist-editor .form-grid,.edge-upstream-dialog .form-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.edge-blocklist-editor .edge-mode-field,.edge-upstream-dialog .form-grid>.collection-select{grid-column:1/-1}.edge-mode-control{display:flex;flex-wrap:wrap;gap:6px}.edge-mode-option{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid var(--line);border-radius:999px;cursor:pointer}.edge-mode-option.is-selected{border-color:var(--accent);background:var(--accent-soft)}.edge-mode-option:focus-within{outline:2px solid var(--accent);outline-offset:2px}.edge-mode-option input{position:absolute;opacity:0;pointer-events:none}@media(max-width:700px){.edge-kind-cards,.edge-blocklist-editor .form-grid,.edge-upstream-dialog .form-grid{grid-template-columns:1fr}.edge-route-row{grid-template-columns:minmax(0,1fr) auto}.edge-route-row>:first-child{grid-column:1/-1}.edge-route-item{grid-template-columns:1fr}.edge-review{grid-template-columns:1fr;gap:2px 0}.edge-review dd{margin-bottom:8px}}
1
+ .edge-form-grid { display: grid; gap: 14px; }
2
+ .edge-server-name { display: grid; gap: 3px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface2); }
3
+ .edge-server-name span, .edge-review dt { font-size: 11px; color: var(--mute); text-transform: uppercase; letter-spacing: .06em; }
4
+ .edge-server-name code { font-size: 15px; overflow-wrap: anywhere; }
5
+ .edge-path-list, .edge-route-list { display: grid; gap: 8px; }
6
+ .edge-path-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: center; }
7
+ .edge-route-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) auto; gap: 8px; align-items: center; }
8
+ .edge-warning { display: flex; gap: 8px; align-items: baseline; padding: 10px 12px; border: 1px solid var(--warn); border-radius: 8px; color: var(--ink); background: var(--surface2); }
9
+ .edge-warning i { color: var(--warn); }
10
+ .edge-note { display: grid; gap: 3px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface2); }
11
+ .edge-note span { color: var(--mute); font-size: 13px; }
12
+ .edge-review { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 8px 16px; margin: 0; }
13
+ .edge-review dd { margin: 0; overflow-wrap: anywhere; }
14
+ .edge-routes { display: grid; gap: 10px; }
15
+ .edge-route-actions, .edge-row-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
16
+ .edge-blocklist-editor .form-grid, .edge-upstream-dialog .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
17
+ .edge-blocklist-editor .edge-mode-field, .edge-upstream-dialog .form-grid > .collection-select { grid-column: 1/-1; }
18
+ .edge-mode-control { display: flex; flex-wrap: wrap; gap: 6px; }
19
+ .edge-mode-option { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border: 1px solid var(--line); border-radius: 999px; cursor: pointer; }
20
+ .edge-mode-option.is-selected { border-color: var(--accent); background: var(--accent-soft); }
21
+ .edge-mode-option:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
22
+ .edge-mode-option input { position: absolute; opacity: 0; pointer-events: none; }
23
+ @media (max-width: 700px) {
24
+ .edge-blocklist-editor .form-grid, .edge-upstream-dialog .form-grid { grid-template-columns: 1fr; }
25
+ .edge-route-row { grid-template-columns: minmax(0, 1fr) auto; }
26
+ .edge-route-row > :first-child { grid-column: 1/-1; }
27
+ .edge-review { grid-template-columns: 1fr; gap: 2px 0; }
28
+ .edge-review dd { margin-bottom: 8px; }
29
+ }
30
+ .edge-serving-page { display: grid; grid-template-columns: minmax(0, 1fr); gap: 24px; min-width: 0; }
31
+ .edge-serving-page .page-header h1, .edge-serving-page .page-header h2 { font-size: 26px; font-weight: 650; letter-spacing: -.025em; line-height: 1.2; }
32
+ .edge-serving-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
33
+ .edge-serving-evidence p { margin: 8px 0; overflow-wrap: anywhere; }
34
+ .edge-serving-check { padding: 12px 0; border-top: 1px solid var(--line); overflow-wrap: anywhere; }
35
+ @media (max-width: 700px) {
36
+ .edge-serving-heading { align-items: flex-start; }
37
+ .edge-serving-page .page-header h1, .edge-serving-page .page-header h2 { font-size: 23px; }
38
+ }
39
+ .edge-serving-page > * { min-width: 0; }
40
+ .edge-vhost-wizard > .modal-title { flex-shrink: 0; margin-bottom: 16px; }
41
+ /* Serving task surfaces are deliberately scoped; shared DetailView is unchanged. */
42
+ .edge-vhost-wizard { display: flex; flex-direction: column; padding: 24px; max-height: calc(100dvh - 32px); }
43
+ .edge-vhost-editor { display: flex; flex-direction: column; min-height: 0; gap: 16px; }
44
+ .edge-vhost-editor > .mojo-tabs { display: flex; flex-direction: column; min-height: 0; }
45
+ .edge-vhost-editor .mojo-tab-panels, .edge-vhost-editor .mojo-tab-panel { min-height: 0; }
46
+ .edge-editor-panel { overflow: auto; min-height: 0; max-height: calc(100dvh - 260px); padding: 18px 2px 4px; }
47
+ .edge-vhost-editor .mojo-tabs { overflow: visible; }
48
+ .edge-editor-actions { flex-shrink: 0; margin-top: 0; padding-top: 16px; border-top: 1px solid var(--line); background: var(--surface); gap: 8px; flex-wrap: wrap; }
49
+ .edge-vhost-wizard .edge-form-grid { gap: 20px; }
50
+ .edge-kind-picker { border: 0; padding: 0; margin: 0; min-width: 0; }
51
+ .edge-kind-picker legend { margin-bottom: 10px; }
52
+ .edge-kind-options { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
53
+ .edge-kind-option { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; cursor: pointer; font-size: 14px; }
54
+ .edge-kind-option.is-selected { border-color: var(--accent); background: var(--accent-soft); }
55
+ .edge-kind-option input { margin: 0; accent-color: var(--accent); }
56
+ .edge-kind-option:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
57
+ .edge-kind-option i { color: var(--accent); }
58
+ .edge-create-progress { display: flex; list-style: none; padding: 0; margin: 0; gap: 16px; flex-wrap: wrap; font-size: 13px; color: var(--mute); }
59
+ .edge-create-progress li { display: flex; align-items: center; gap: 6px; }
60
+ .edge-create-progress li > span { display: grid; place-items: center; width: 22px; height: 22px; border: 1px solid var(--line); border-radius: 50%; font-size: 12px; }
61
+ .edge-create-progress li[aria-current="step"] { color: var(--accent); font-weight: 600; }
62
+ .edge-create-progress li[aria-current="step"] > span { background: var(--accent-soft); border-color: var(--accent); }
63
+ .edge-help { display: inline-flex; vertical-align: middle; }
64
+ .edge-help > .btn-icon { width: 26px; height: 26px; min-width: 26px; color: var(--mute); }
65
+ .edge-help-popover { width: min(320px, calc(100vw - 32px)); max-width: calc(100vw - 32px); padding: 14px 16px; font-size: 13px; line-height: 1.5; white-space: normal; overflow-wrap: anywhere; }
66
+ .edge-help-popover p { margin: 0; }
67
+ .edge-hostname-detail { padding: 24px; display: flex; flex-direction: column; min-height: 0; max-height: calc(100dvh - 32px); }
68
+ .edge-detail-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; padding-bottom: 20px; }
69
+ .edge-detail-identity { min-width: 0; }
70
+ .edge-detail-identity .modal-title { margin: 0 0 8px; overflow-wrap: anywhere; }
71
+ .edge-detail-subtitle { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; color: var(--mute); font-size: 14px; }
72
+ .edge-detail-actions { display: flex; flex-shrink: 0; align-items: center; gap: 8px; }
73
+ .edge-enabled-switch { display: flex; gap: 6px; align-items: center; font-size: 13px; color: var(--mute); }
74
+ .edge-enabled-switch input { appearance: none; width: 34px; height: 20px; border-radius: 12px; border: 1px solid var(--line); background: var(--surface2); cursor: pointer; position: relative; }
75
+ .edge-enabled-switch input:before { content: ''; position: absolute; width: 14px; height: 14px; left: 2px; top: 2px; border-radius: 50%; background: var(--mute); }
76
+ .edge-enabled-switch input:checked { background: var(--accent); border-color: var(--accent); }
77
+ .edge-enabled-switch input:checked:before { left: 16px; background: var(--surface); }
78
+ .edge-enabled-switch input:disabled { opacity: .55; cursor: default; }
79
+ .edge-enabled-switch input:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
80
+ .edge-hostname-detail > .mojo-tabs { min-height: 0; overflow: auto; }
81
+ .edge-detail-panel { padding: 16px 2px 4px; min-height: 0; overflow-wrap: anywhere; }
82
+ .edge-detail-panel > p { margin: 16px 0; }
83
+ .edge-detail-panel .flat-row { min-width: 0; }
84
+ .edge-detail-panel .flat-row code { overflow-wrap: anywhere; }
85
+ .edge-detail-panel > div:last-child > .btn { margin-top: 16px; }
86
+ .edge-hostname-detail .form-alert { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-bottom: 16px; }
87
+ .edge-action-menu { padding: 8px; }
88
+ .edge-route-dialog .edge-form-grid { margin-top: 20px; }
89
+ .edge-check-summary { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
90
+ .edge-check-status { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
91
+ .edge-check-status > .bi { color: var(--accent); }
92
+ .edge-serving-evidence { min-width: 0; margin-top: 12px; display: grid; gap: 4px; font-size: 13px; }
93
+ .edge-serving-evidence > small { font-size: 12px; }
94
+ .edge-check-dialog { max-height: calc(100dvh - 32px); display: flex; flex-direction: column; }
95
+ .edge-check-body { overflow: auto; min-height: 0; }
96
+ .edge-check-dialog .modal-actions { flex-shrink: 0; }
97
+ .edge-domain-filter { width: min(320px, 100%); }
98
+ .edge-domain-filter .field-label { font-size: 13px; }
99
+ .edge-settings-button { justify-self: start; }
100
+ @media (max-width: 700px) {
101
+ .edge-vhost-wizard, .edge-hostname-detail { padding: 20px 16px; }
102
+ .edge-detail-header { flex-wrap: wrap; gap: 12px; }
103
+ .edge-detail-actions { width: 100%; justify-content: flex-end; }
104
+ .edge-detail-actions > .edge-enabled-switch { margin-right: auto; }
105
+ .edge-create-progress { gap: 8px 12px; font-size: 12px; }
106
+ .edge-create-progress li > span { width: 20px; height: 20px; }
107
+ .edge-editor-panel { max-height: calc(100dvh - 285px); }
108
+ .edge-route-row > .collection-select { min-width: 0; }
109
+ .edge-check-summary { align-items: flex-start; }
110
+ .edge-serving-evidence { width: 100%; }
111
+ }
112
+ @media (max-width: 700px) {
113
+ .edge-vhost-editor .mojo-tab, .edge-hostname-detail .mojo-tab { font-size: 13px; padding-left: 8px; padding-right: 8px; }
114
+ }
115
+ .edge-vhost-editor .edge-route-row { align-items: end; }
116
+ .edge-hostname-detail .edge-route-actions .btn { white-space: nowrap; }
117
+
118
+ .edge-serving-heading.page-header { align-items: flex-start; }
119
+ .edge-serving-heading.page-header > div { min-width: 0; }
120
+ .edge-serving-evidence .edge-row-actions { align-items: center; gap: 6px; }
121
+ .edge-serving-evidence .btn-compact { font-size: 12px; padding: 4px 8px; }
122
+ .edge-serving-evidence .btn-icon { width: 28px; height: 28px; }
123
+
124
+ .edge-certificate-summary { display: grid; gap: 8px; min-width: 0; }
125
+ .edge-certificate-summary > .btn { justify-self: start; }
@@ -0,0 +1,40 @@
1
+ .geo-admin { display: grid; max-width: 880px; margin-inline: auto; gap: 20px; min-width: 0; overflow-wrap: anywhere; container-type: inline-size; }
2
+ .geo-admin .page-header { display: flex; justify-content: space-between; margin: 0; align-items: start; flex-wrap: wrap; gap: 12px; }
3
+ .geo-admin h1 { font-size: 26px; font-weight: 750; line-height: 1.2; letter-spacing: -.025em; color: var(--ink-strong); margin: 4px 0 8px; }
4
+ .geo-admin h2 { font-size: 16px; font-weight: 700; line-height: 1.35; color: var(--ink-strong); margin: 0; }
5
+ .geo-admin-setup, .geo-admin-status { min-width: 0; }
6
+ .geo-admin-form { padding: 24px; }
7
+ .geo-admin-provider-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
8
+ .geo-admin-fields { border: 0; padding: 0; margin: 0; min-width: 0; display: grid; gap: 22px; }
9
+ .geo-admin .field { display: grid; gap: 8px; min-width: 0; }
10
+ .geo-admin .field-label, .geo-admin-key-heading, .geo-admin-sharing { display: flex; gap: 8px; align-items: center; }
11
+ .geo-admin .input { width: 100%; box-sizing: border-box; min-width: 0; }
12
+ .geo-admin [hidden] { display: none !important; }
13
+ .geo-admin-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
14
+ .geo-admin-actions .btn { white-space: normal; }
15
+ .geo-admin-key { display: grid; gap: 12px; padding: 16px; background: var(--surface2); border: 1px solid var(--line); border-radius: 8px; min-width: 0; }
16
+ .geo-admin-key p, .geo-admin-result p, .geo-admin-status p, .geo-admin-notice p { margin: 0; }
17
+ .geo-admin-provider-list { padding: 0; margin: 0; list-style: none; display: grid; gap: 8px; }
18
+ .geo-admin-provider-list li { display: flex; justify-content: space-between; align-items: center; gap: 8px; border: 1px solid var(--line); border-radius: 6px; padding: 8px 10px; }
19
+ .geo-admin-provider-list small { display: block; }
20
+ .geo-admin-provider-list .btn { padding: 5px 8px; }
21
+ .geo-admin-context, .geo-admin-result { margin: 0 24px 20px; }
22
+ .geo-admin-result { display: grid; gap: 8px; }
23
+ .geo-admin-footer { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; padding: 16px 24px; border-top: 1px solid var(--line); }
24
+ .geo-admin-status { padding: 18px 24px; display: grid; gap: 10px; }
25
+ .geo-admin-status-heading, .geo-admin-activation { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }
26
+ .geo-admin-status-heading .btn { font-size: 12px; padding: 5px 8px; }
27
+ .geo-admin-notice { border: 1px solid var(--line); background: var(--accent-soft); border-radius: 8px; padding: 12px; margin: 0; display: grid; gap: 6px; }
28
+ .geo-admin-dialog dl { display: grid; gap: 14px; }
29
+ .geo-admin-dialog dt { color: var(--mute); font-size: 12px; margin-bottom: 4px; }
30
+ .geo-admin-dialog dd { margin: 0; overflow-wrap: anywhere; }
31
+ @container (max-width: 420px) {
32
+ .geo-admin-provider-fields { grid-template-columns: 1fr; }
33
+ .geo-admin-form, .geo-admin-status { padding: 18px; }
34
+ .geo-admin-key { padding: 12px; }
35
+ .geo-admin-provider-list li { flex-wrap: wrap; }
36
+ .geo-admin-actions { gap: 6px; }
37
+ .geo-admin-footer { padding: 16px 18px; }
38
+ .geo-admin-context, .geo-admin-result { margin-inline: 18px; }
39
+ .geo-admin-sharing { align-items: start; }
40
+ }
@@ -0,0 +1,127 @@
1
+ .admin-maintenance {
2
+ display: grid;
3
+ gap: 1.25rem;
4
+ max-width: 80rem;
5
+ margin: auto;
6
+ padding: 0;
7
+ overflow-wrap: anywhere;
8
+ }
9
+ .admin-maintenance header,
10
+ .maintenance-resource {
11
+ display: flex;
12
+ align-items: flex-start;
13
+ justify-content: space-between;
14
+ gap: 1rem;
15
+ }
16
+ .admin-maintenance section {
17
+ background: var(--surface);
18
+ border: 1px solid var(--line);
19
+ border-radius: var(--radius-lg, 0.75rem);
20
+ padding: 1.25rem;
21
+ min-width: 0;
22
+ }
23
+ .admin-maintenance h1,
24
+ .admin-maintenance h2,
25
+ .admin-maintenance h3 {
26
+ margin: 0 0 0.5rem;
27
+ }
28
+ .maintenance-resource {
29
+ padding: 1rem 0;
30
+ border-bottom: 1px solid var(--line);
31
+ }
32
+ .maintenance-resource:last-child {
33
+ border-bottom: 0;
34
+ }
35
+ .admin-maintenance p {
36
+ margin: 0.5rem 0;
37
+ }
38
+ .admin-maintenance dl {
39
+ display: grid;
40
+ grid-template-columns: minmax(7rem, auto) 1fr;
41
+ gap: 0.5rem 1rem;
42
+ }
43
+ .admin-maintenance dd {
44
+ margin: 0;
45
+ }
46
+ .maintenance-warning {
47
+ color: var(--warn);
48
+ }
49
+ .maintenance-mode {
50
+ color: var(--mute);
51
+ }
52
+ .maintenance-dialog {
53
+ color: var(--ink);
54
+ background: var(--surface);
55
+ border: 1px solid var(--line);
56
+ border-radius: var(--radius-lg, 0.75rem);
57
+ max-width: min(38rem, calc(100vw - 2rem));
58
+ max-height: calc(100vh - 2rem);
59
+ overflow: auto;
60
+ }
61
+ .maintenance-dialog::backdrop {
62
+ background: rgb(0 0 0 / 0.6);
63
+ }
64
+ .maintenance-dialog-body {
65
+ padding: 1.25rem;
66
+ display: grid;
67
+ gap: 1rem;
68
+ }
69
+ .maintenance-dialog h2,
70
+ .maintenance-dialog p {
71
+ margin: 0;
72
+ }
73
+ .maintenance-dialog fieldset {
74
+ display: grid;
75
+ gap: 0.75rem;
76
+ }
77
+ .maintenance-confirm-label {
78
+ display: grid;
79
+ gap: 0.5rem;
80
+ }
81
+ .maintenance-confirm-label input {
82
+ width: 100%;
83
+ min-width: 0;
84
+ background: var(--field-bg, var(--surface));
85
+ color: var(--ink);
86
+ border: 1px solid var(--line);
87
+ padding: 0.65rem;
88
+ border-radius: 0.35rem;
89
+ }
90
+ .maintenance-actions {
91
+ display: flex;
92
+ justify-content: flex-end;
93
+ gap: 0.75rem;
94
+ }
95
+ @media (max-width: 600px) {
96
+ .admin-maintenance {
97
+ padding: 0;
98
+ }
99
+ .admin-maintenance header,
100
+ .maintenance-resource {
101
+ flex-direction: column;
102
+ }
103
+ .admin-maintenance section {
104
+ padding: 0.8rem;
105
+ }
106
+ .maintenance-actions {
107
+ flex-wrap: wrap;
108
+ }
109
+ }
110
+
111
+ .admin-maintenance h1{font-size:26px;font-weight:750;line-height:1.2;letter-spacing:-.025em;color:var(--ink-strong)}.admin-maintenance h2,.maintenance-dialog h2{font-size:18px;font-weight:700;line-height:1.35;color:var(--ink-strong)}.admin-maintenance h3{font-size:16px;font-weight:650;line-height:1.4;color:var(--ink-strong)}
112
+
113
+ .maintenance-resource>div{min-width:0}.maintenance-resource>button{flex-shrink:0}.maintenance-resource-status{color:var(--mute)}.maintenance-details{margin-block:.65rem}.maintenance-details>summary{font-size:13px;font-weight:600;cursor:pointer;color:var(--mute)}.maintenance-details[open]>summary{margin-bottom:.65rem}.admin-maintenance .maintenance-framework-summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem}.maintenance-framework-summary>div{min-width:0}.maintenance-framework-summary dt{font-size:12px;color:var(--mute);margin-bottom:4px}.maintenance-framework-summary dd{font-size:16px;font-weight:600;overflow-wrap:anywhere}.admin-maintenance .maintenance-resource h3{margin-bottom:.25rem}.admin-maintenance .maintenance-resource p{margin:.25rem 0}.admin-maintenance .maintenance-details p{margin:.5rem 0}@media(max-width:600px){.admin-maintenance .maintenance-framework-summary{grid-template-columns:1fr}.maintenance-resource>button{align-self:flex-start}}
114
+
115
+ .admin-maintenance { gap: 24px; }
116
+ .admin-maintenance header > div > p:last-child { color: var(--mute); }
117
+ .admin-maintenance section.maintenance-scan { padding: 0; background: transparent; border: 0; }
118
+ .maintenance-scan .ops-details > summary { padding: 0; }
119
+ .maintenance-dialog { margin: auto; padding: 0; width: min(38rem, calc(100vw - 2rem)); }
120
+ .maintenance-dialog fieldset { border: 1px solid var(--line); border-radius: 8px; padding: 12px 16px; }
121
+ .maintenance-dialog legend { padding-inline: 4px; font-weight: 600; }
122
+ .maintenance-dialog-body { padding: 24px; gap: 20px; }
123
+
124
+ .maintenance-dialog input[type="radio"] { margin-right: 6px; }
125
+
126
+ /* Maintenance-only dialogs and compact progress. No shared Dashboard rules. */
127
+ .maintenance-dialog-body{max-height:calc(100dvh - 2rem);display:flex;flex-direction:column;overflow:hidden;box-sizing:border-box}.maintenance-dialog-scroll{min-height:0;overflow:auto;display:grid;gap:16px;padding:2px}.maintenance-dialog .maintenance-actions{flex-shrink:0;padding-top:12px;align-items:center}.maintenance-dialog .maintenance-progress-facts{display:grid;grid-template-columns:auto minmax(0,1fr);gap:8px 16px;margin:0}.maintenance-progress-facts dt{color:var(--mute)}.maintenance-progress-facts dd{margin:0;overflow-wrap:anywhere}.maintenance-attempt-row{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:12px 0;border-bottom:1px solid var(--line)}.maintenance-attempt-row:last-of-type{border-bottom:0}.admin-maintenance .maintenance-actions{justify-content:flex-start;margin-top:16px;flex-wrap:wrap}.maintenance-dialog .maintenance-dialog-scroll>p{margin:0}.maintenance-dialog .maintenance-dialog-scroll fieldset{margin:0}.maintenance-dialog .maintenance-dialog-scroll .field{margin:0}.maintenance-attempt-row>div{min-width:0}@media(max-width:600px){.maintenance-dialog-body{padding:20px;gap:16px}.maintenance-attempt-row{align-items:flex-start;flex-direction:column}.maintenance-dialog .maintenance-actions{gap:8px}.maintenance-dialog .maintenance-actions .btn{max-width:100%;white-space:normal}}
@@ -0,0 +1,38 @@
1
+ .operational-fleet{display:grid;gap:16px;min-width:0;container-type:inline-size;overflow-wrap:anywhere}.fleet-heading,.fleet-buttons{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.fleet-heading h2{margin:0}.fleet-heading p{margin:4px 0 0}.fleet-evidence-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.fleet-evidence{min-width:0;overflow:hidden}.fleet-evidence h3{padding:14px 16px;margin:0;border-bottom:1px solid var(--line)}.fleet-evidence-row{display:grid;gap:6px;padding:12px 16px;border-bottom:1px solid var(--line);min-width:0}.fleet-evidence-row:last-child{border-bottom:0}.fleet-evidence-row>div:first-child{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap}.fleet-evidence-row p{margin:2px 0 5px}.fleet-source-status{font-size:11px;letter-spacing:.03em;text-transform:uppercase;color:var(--mute)}.fleet-status-healthy{color:var(--ok)}.fleet-status-unhealthy{color:var(--bad)}.fleet-status-degraded,.fleet-status-stale{color:var(--warn)}.fleet-availability{border-left:3px solid var(--mute)}.fleet-availability-ok{border-left-color:var(--ok)}.fleet-availability-down{border-left-color:var(--bad)}.fleet-capacity-facts summary{cursor:pointer;font-weight:600}.fleet-fact-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px}.fleet-member{display:block;margin:4px 0;color:var(--mute)}.fleet-scale{display:flex;flex-direction:column;max-height:calc(100dvh - 80px);min-height:0;overflow-wrap:anywhere}.fleet-scale-body{overflow:auto;min-height:0;padding-right:4px}.fleet-scale h3{margin:20px 0 8px}.fleet-scale-fields{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.fleet-scale-fields .field{min-width:0}.fleet-node{display:flex;gap:12px;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid var(--line)}.fleet-node small{display:block}.fleet-plan-steps{padding-left:24px}.fleet-plan-steps li{margin-bottom:14px}.fleet-progress-step{padding:10px 0;border-bottom:1px solid var(--line)}.fleet-scale .modal-actions{flex-shrink:0}.fleet-scale fieldset{border:0;margin:0;padding:0;min-width:0}@container (max-width:850px){.fleet-evidence-grid{grid-template-columns:1fr}.fleet-fact-grid{grid-template-columns:1fr}}@media(max-width:560px){.fleet-scale-fields{grid-template-columns:1fr}.fleet-node{align-items:flex-start;flex-direction:column}.fleet-scale{max-height:calc(100dvh - 48px)}}
2
+
3
+ .fleet-page>.page-header{display:flex;justify-content:space-between;align-items:start;margin-bottom:18px}.fleet-page h1{font-size:26px;font-weight:750;line-height:1.2;letter-spacing:-.025em;color:var(--ink-strong);margin:0}.fleet-heading h2{font-size:20px;font-weight:700;line-height:1.3;color:var(--ink-strong)}.fleet-evidence h3{font-size:16px;font-weight:700;color:var(--ink-strong)}.fleet-availability>strong{font-size:18px;line-height:1.35;font-weight:700;color:var(--ink-strong)}.fleet-scale h3{font-size:17px;font-weight:700;color:var(--ink-strong)}.fleet-scale h4,.fleet-fact-grid h4{font-size:15px;font-weight:650;color:var(--ink-strong)}.fleet-scale .fleet-scale-disclosure{border-top:1px solid var(--line);padding:12px 0}.fleet-scale-disclosure>summary{font-size:15px;font-weight:650;cursor:pointer;color:var(--ink-strong)}.fleet-scale-disclosure[open]>summary{margin-bottom:14px}.fleet-scale fieldset>h3:first-child{margin-top:12px}.fleet-scale fieldset>p{margin-bottom:12px}.fleet-scale fieldset>.fleet-scale-fields{margin-bottom:14px}
4
+
5
+ .fleet-evidence-grid { grid-template-columns: minmax(0, 1fr); gap: 24px; }
6
+ .fleet-evidence { border: 0; border-radius: 0; box-shadow: none; background: transparent; }
7
+ .fleet-evidence h3 { padding: 0 0 10px; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--mute); }
8
+ .fleet-evidence-row { grid-template-columns: minmax(130px, .7fr) minmax(0, 2fr) auto 62px; align-items: center; gap: 16px; padding: 17px 0; }
9
+ .fleet-row-value { min-width: 0; color: var(--mute); }
10
+ .fleet-detail-button { border: 0; padding: 4px 0; background: transparent; color: var(--accent); cursor: pointer; text-align: right; }
11
+ .fleet-source-detail > p { margin: 14px 0; }
12
+ .operational-fleet { gap: 24px; }
13
+ @container (max-width: 600px) { .fleet-evidence-row { grid-template-columns: minmax(0, 1fr) auto; gap: 6px 12px; } .fleet-row-value { grid-row: 2; } .fleet-source-status { grid-column: 2; grid-row: 1; } .fleet-detail-button { grid-column: 2; grid-row: 2; } }
14
+
15
+ .fleet-capacity-tab { padding-top: 4px; }
16
+ .fleet-scale-body .fleet-buttons { margin-block: 10px 18px; }
17
+ .fleet-scale-body > fieldset { margin-block: 12px 20px; }
18
+ .fleet-scale-body > section { margin-block: 16px; }
19
+ .fleet-scale .modal-actions { padding-top: 16px; border-top: 1px solid var(--line); }
20
+ .fleet-scale .input { width: 100%; min-width: 0; }
21
+
22
+ .fleet-evidence.panel { padding: 0; border: 0; border-radius: 0; box-shadow: none; background: transparent; }
23
+ .fleet-evidence-row { padding-block: 14px; }
24
+
25
+ .fleet-heading h2 { font-size: 26px; letter-spacing: -.025em; }
26
+
27
+ .fleet-heading h1 { margin: 0; font-size: 26px; line-height: 1.3; font-weight: 700; letter-spacing: -.025em; color: var(--ink-strong); }
28
+
29
+ .fleet-capacity-tab .panel > p { margin: 8px 0 16px; }
30
+ .fleet-scale-fields .field { gap: 8px; }
31
+
32
+ .fleet-capacity-dialog { display: flex; flex-direction: column; max-height: calc(100dvh - 64px); min-height: 0; overflow-wrap: anywhere; }
33
+ .fleet-capacity-body { overflow: auto; min-height: 0; }
34
+ .fleet-capacity-body p { margin: 8px 0; line-height: 1.5; }
35
+ .fleet-capacity-body h4 { margin: 0 0 8px; }
36
+ .fleet-capacity-body > .dim { margin: 0 0 18px; }
37
+ .fleet-capacity-dialog .modal-actions { flex: none; padding-top: 16px; border-top: 1px solid var(--line); }
38
+ @media (max-width: 560px) { .fleet-capacity-dialog .fleet-fact-grid { grid-template-columns: minmax(0, 1fr); } }
@@ -0,0 +1,9 @@
1
+ .ops-help { display: inline-flex; align-items: center; justify-content: center; padding: 3px; border: 0; background: transparent; color: var(--mute); cursor: pointer; vertical-align: middle; font-size: 14px; line-height: 1; border-radius: 4px; }
2
+ .ops-help:hover, .ops-help[aria-expanded="true"] { color: var(--accent); background: var(--accent-soft); }
3
+ .ops-help-content { width: min(340px, calc(100vw - 32px)); padding: 14px 16px; font-size: 13px; line-height: 1.6; color: var(--ink); overflow-wrap: anywhere; }
4
+ .ops-help-content p { margin: 0 0 8px; }
5
+ .ops-help-content p:last-child { margin-bottom: 0; }
6
+ .ops-details > summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--mute); padding-block: 6px; }
7
+ .ops-details[open] > summary { margin-bottom: 10px; }
8
+ .ops-cell { display: grid; gap: 3px; min-width: 0; }
9
+ .ops-cell small { color: var(--mute); font-size: 12px; }
@@ -52,6 +52,8 @@ textarea.signin-code { resize: vertical; }
52
52
  .signin-note i { flex: none; color: var(--accent); }
53
53
  .signin-note.is-warning { border-color: color-mix(in srgb, var(--warn) 35%, var(--line)); background: var(--warn-soft); }
54
54
  .signin-note.is-warning i { color: var(--warn); }
55
+ .signin-note.is-danger { border-color: color-mix(in srgb, var(--bad) 35%, var(--line)); background: var(--bad-soft); }
56
+ .signin-note.is-danger i { color: var(--bad); }
55
57
  .signin-provider-actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 12px; border-top: 1px solid var(--line); }
56
58
 
57
59
  @media (max-width: 1100px) { .signin-groups { grid-template-columns: 1fr; } }
@@ -1,7 +1,6 @@
1
1
  /* ComboBox — styles for portal-mojo <ComboBox> (packages/portal-mojo/src/ui/ComboBox.tsx).
2
2
  Tokens only; reads correctly under data-theme="light" AND "dark".
3
- NOT imported yet — the orchestrator adds `@import "./theme/combobox.css";`
4
- to theme.css at merge. */
3
+ */
5
4
 
6
5
  .combo { position: relative; width: 100%; }
7
6
  .combo-input-wrap { position: relative; }
@@ -30,6 +29,8 @@
30
29
  /* Keyboard highlight (.combo-hot) and hover share one look — there is only
31
30
  one "pointed at" state. */
32
31
  .combo-option:hover, .combo-option.combo-hot { background: var(--surface2); color: var(--ink-strong); }
32
+ .combo-option[aria-disabled="true"] { opacity: .5; cursor: not-allowed; background: transparent; }
33
+ .combo-input[aria-invalid="true"] { border-color: var(--bad); }
33
34
  .combo-option-main { flex: 1; min-width: 0; }
34
35
  .combo-option-label { font-weight: 550; }
35
36
  .combo-option-desc { font-size: 12px; color: var(--mute); margin-top: 1px; }
@@ -40,3 +41,12 @@
40
41
  /* The <mark> around the matched substring — override the UA's yellow/black
41
42
  so it stays legible on both themes. */
42
43
  .combo-mark { background: var(--warn-soft); color: inherit; border-radius: 3px; padding: 0 1px; }
44
+
45
+ .page-size .combo { width: 78px; }
46
+ .combo { min-width: 0; }
47
+ .combo-input { width: 100%; min-width: 0; }
48
+ .combo-pop { max-height: min(300px, calc(100dvh - 16px)); min-width: min(260px, calc(100vw - 16px)); }
49
+
50
+ .page-size .combo-input { min-height: 30px; padding: 5px 30px 5px 9px; font-size: 12px; }
51
+ .combo.gran-select { width: 110px; }
52
+ .gran-select .combo-input { min-height: 30px; padding: 5px 30px 5px 9px; font-size: 12px; }
@@ -0,0 +1,9 @@
1
+ /* Standalone Fleet only. Dashboard FleetOverview retains its own presentation. */
2
+ .fleet-page .fp-workspace{display:flex;flex-direction:column;gap:20px;min-width:0}.fleet-page .fp-header{display:flex;align-items:center;justify-content:space-between;gap:24px}.fleet-page .fp-header h1{margin:3px 0 4px}.fleet-page .fp-header p{margin:0}.fleet-page .fp-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-end;gap:8px}.fleet-page .fp-health{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:20px 24px;border-left:3px solid var(--ok)}.fleet-page .fp-health-down{border-left-color:var(--bad)}.fleet-page .fp-health-unknown{border-left-color:var(--warn)}.fleet-page .fp-health p{margin:6px 0 0}.fleet-page .fp-evidence-grid{display:grid;grid-template-columns:1fr 1fr;align-items:start;gap:0}.fleet-page .fp-section{overflow:hidden;padding:8px 18px}.fleet-page .fp-section .mojo-tab-panel{padding:4px 0}.fleet-page .fp-section .mojo-tab-list{margin:0 2px}.fleet-page .fp-section h2{font-size:14px;margin:0;padding:18px 20px;border-bottom:1px solid var(--line)}.fleet-page .fp-source{padding:14px 12px;border-bottom:1px solid var(--line);min-width:0}.fleet-page .fp-source:nth-last-child(-n+2){border-bottom:0}.fleet-page .fp-source:last-child{border-bottom:0}.fleet-page .fp-source p{margin:5px 0 3px}.fleet-page .fp-row,.fleet-standalone-dialog .fp-row{display:flex;align-items:center;justify-content:space-between;gap:12px}.fleet-page .fp-source-meta{font-size:11px;color:var(--mute);gap:8px;flex-wrap:wrap}.fleet-page .fp-state,.fleet-standalone-dialog .fp-state{font-size:12px;font-weight:600;text-transform:capitalize}.fleet-page .fp-capacity-strip{display:grid;grid-template-columns:repeat(3,1fr) 1.6fr;padding:18px 24px;gap:18px}.fleet-page .fp-capacity-strip>div{display:flex;flex-direction:column;gap:4px}.fleet-page .fp-capacity-strip strong{font-size:23px;line-height:1.3}.fleet-page .fp-capacity-context{justify-content:center;align-items:flex-end}.fleet-page .fp-operation{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 24px}.fleet-page .fp-operation p{margin:6px 0 0}.fleet-page .fp-restricted{padding:16px 20px;margin:0}.fleet-page .fp-access-note{font-size:12px;margin:0}.fleet-standalone-dialog{display:flex;flex-direction:column;max-height:min(850px,calc(100dvh - 60px));min-width:0}.fleet-standalone-dialog .fleet-standalone-body{overflow:auto;min-height:0;overscroll-behavior:contain;padding:2px 2px 12px}.fleet-standalone-dialog .modal-message{flex-shrink:0}.fleet-standalone-dialog .modal-actions{flex-shrink:0}.fleet-standalone-dialog fieldset{min-width:0;border:0;padding:0;margin:0}.fleet-standalone-dialog .fp-stack{display:flex;flex-direction:column;gap:16px}.fleet-standalone-dialog .fp-stack>p,.fleet-standalone-dialog .fp-resource p,.fleet-standalone-dialog .fp-resource h3{margin:0}.fleet-standalone-dialog .mojo-tab-panel{padding-top:20px}.fleet-standalone-dialog .fp-resource{border:1px solid var(--line);border-radius:10px;padding:18px;overflow-wrap:anywhere}.fleet-standalone-dialog .fp-resource>p+small,.fleet-standalone-dialog .fp-resource>p+p{display:block;margin-top:8px}.fleet-standalone-dialog .fp-resource h3{font-size:14px}.fleet-standalone-dialog .fp-member{display:flex;flex-direction:column;gap:6px;padding:14px 0;border-bottom:1px solid var(--line)}.fleet-standalone-dialog .fp-member:last-child{border-bottom:0}.fleet-standalone-dialog .fp-fields{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start}.fleet-standalone-dialog .fp-check{display:flex;align-items:flex-start;gap:9px;font-size:13px;line-height:1.6}.fleet-standalone-dialog .fp-check input{flex:none;margin-top:4px}.fleet-standalone-dialog .fp-toolbar{margin:0 0 18px;flex-wrap:wrap}.fleet-standalone-dialog .fp-empty{margin:20px 0 4px}.fleet-standalone-dialog .fp-plan{display:flex;flex-direction:column;gap:14px;padding-left:24px;margin:0}.fleet-standalone-dialog .fp-plan p{margin:6px 0 0}.fleet-standalone-dialog pre{white-space:pre-wrap;font-family:var(--font-mono,monospace);font-size:12px}.fleet-standalone-dialog .field-help{line-height:1.6}.fleet-standalone-dialog .input{width:100%;min-width:0}.fleet-standalone-dialog .modal-title{overflow-wrap:anywhere}.fleet-standalone-dialog .fp-member .fp-fields{margin-top:8px}
3
+ @media(max-width:700px){.fleet-page .fp-header{align-items:flex-start;flex-direction:column;gap:16px}.fleet-page .fp-actions{justify-content:flex-start}.fleet-page .fp-evidence-grid{grid-template-columns:1fr}.fleet-page .fp-capacity-strip{grid-template-columns:repeat(3,1fr);padding:16px;gap:16px}.fleet-page .fp-capacity-context{grid-column:1/-1;align-items:flex-start}.fleet-page .fp-health,.fleet-page .fp-operation{padding:18px;align-items:flex-start}.fleet-page .fp-operation{flex-direction:column}.fleet-standalone-dialog{max-height:calc(100dvh - 32px)}.fleet-standalone-dialog .fp-fields{grid-template-columns:1fr}.fleet-standalone-dialog .fp-resource>.fp-row{align-items:flex-start;flex-wrap:wrap}.fleet-standalone-dialog .fp-resource{padding:14px}.fleet-page .fp-source{padding:12px 4px}.fleet-page .fp-source:nth-last-child(2){border-bottom:1px solid var(--line)}.fleet-page .fp-section{padding:8px 14px}}
4
+ /* Keep every inspection tab visible within a narrow native dialog. */
5
+ @media(max-width:700px){.fleet-standalone-dialog .mojo-tab-list{flex-wrap:wrap;overflow:visible}}
6
+
7
+ /* Destination names are actions; keep evidence-only labels visually distinct. */
8
+ .fleet-page .fp-source strong a{color:var(--accent);text-decoration:underline;text-underline-offset:3px}
9
+ .fleet-page .fp-source strong a:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:2px}
@@ -24,6 +24,13 @@
24
24
  @import "./components/admin-assistant.css" layer(portal-mojo);
25
25
  @import "./components/admin-security.css" layer(portal-mojo);
26
26
  @import "./components/admin-dashboard.css" layer(portal-mojo);
27
+ @import "./components/admin-operational-fleet.css" layer(portal-mojo);
28
+ @import "./components/fleet-page.css" layer(portal-mojo);
29
+ @import "./components/admin-maintenance.css" layer(portal-mojo);
30
+ @import "./components/admin-deployments.css" layer(portal-mojo);
31
+ @import "./components/admin-domains.css" layer(portal-mojo);
32
+ @import "./components/admin-operations.css" layer(portal-mojo);
33
+ @import "./components/admin-geolocation.css" layer(portal-mojo);
27
34
  @import "./components/admin-cloudwatch.css" layer(portal-mojo);
28
35
  @import "./components/admin-monitoring.css" layer(portal-mojo);
29
36
  @import "./components/admin-bouncer.css" layer(portal-mojo);
@@ -35,15 +35,24 @@
35
35
  // · "Start typing to search…" is actually reachable — in the source it
36
36
  // was dead code (its showNoResults guard required hasSearched).
37
37
  import { useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from 'react';
38
- import { useQueryClient, type QueryClient } from '@tanstack/react-query';
39
- import { useModel, useModelList } from '../client/hooks';
38
+ import { keepPreviousData, useQuery, useQueryClient, type QueryClient } from '@tanstack/react-query';
39
+ import { mojoGet, mojoList } from '../client/client';
40
+ import { getAuthSessionRevision } from '../client/auth';
40
41
  import type { ModelDef } from '../client/model';
41
42
  import type { Params } from '../client/types';
43
+ import { useAuthSnapshot } from '../client/me';
42
44
  import { GroupContext } from '../client/group-context';
43
45
  import { Popover } from './Popover';
44
46
 
45
47
  /** Every id comparison in this component goes through here — string and
46
48
  * number ids (and mixes of the two) work by construction. */
49
+ const sanitizerIds = new WeakMap<Function, number>();
50
+ let nextSanitizerId = 1;
51
+ function sanitizerId(fn: Function): number {
52
+ if (!sanitizerIds.has(fn)) sanitizerIds.set(fn, nextSanitizerId++);
53
+ return sanitizerIds.get(fn)!;
54
+ }
55
+
47
56
  const keyOf = (v: string | number): string => String(v);
48
57
 
49
58
  /** MOJOUtils.getNestedValue heritage: dot-notation nested access. */
@@ -76,8 +85,8 @@ const coerceFetchId = (id: string | number): string | number =>
76
85
  * root for the row (ModelTable pages, other pickers, this picker's own
77
86
  * fetches all share the [endpoint, params] keys). Non-reactive by design:
78
87
  * a miss falls through to the reactive one-record fetch. */
79
- function findRowInListCaches(qc: QueryClient, endpoint: string, key: string, valueField: string): unknown {
80
- for (const [, data] of qc.getQueriesData<unknown>({ queryKey: [endpoint] })) {
88
+ function findRowInListCaches(qc: QueryClient, endpoint: string, key: string, valueField: string, safeRoot?: readonly unknown[]): unknown {
89
+ for (const [, data] of qc.getQueriesData<unknown>({ queryKey: safeRoot ?? [endpoint] })) {
81
90
  if (data == null || typeof data !== 'object') continue;
82
91
  const rows = (data as { rows?: unknown }).rows;
83
92
  if (!Array.isArray(rows)) continue; // skips [endpoint,'one',id] entries
@@ -109,6 +118,8 @@ export type CollectionSelectValue = string | number | Record<string, unknown> |
109
118
  export interface CollectionSelectProps<T extends { id: number | string }> {
110
119
  /** A defineModel definition — supplies the endpoint (and the row type). */
111
120
  model?: ModelDef<T>;
121
+ /** Optional ingress projection. Uses an auth-scoped isolated cache; raw caches are never read. */
122
+ sanitizeRow?: (row: T) => T;
112
123
  /** Bare endpoint, for lists without a model definition. */
113
124
  endpoint?: string;
114
125
  /** Selected id — CONTROLLED. Accepts id | null | row-object (see CollectionSelectValue). */
@@ -156,7 +167,7 @@ export interface CollectionSelectProps<T extends { id: number | string }> {
156
167
  }
157
168
 
158
169
  export function CollectionSelect<T extends { id: number | string }>({
159
- model, endpoint, value, onChange,
170
+ model, endpoint, value, onChange, sanitizeRow,
160
171
  labelField = 'name', valueField = 'id',
161
172
  maxItems = 10, placeholder = 'Search…', debounceMs = 400,
162
173
  emptyFetch = true, defaultParams, requiresActiveGroup = false,
@@ -167,6 +178,8 @@ export function CollectionSelect<T extends { id: number | string }>({
167
178
  if (!resolvedEndpoint) throw new Error('CollectionSelect: pass `model` or `endpoint`');
168
179
 
169
180
  const qc = useQueryClient();
181
+ const auth = useAuthSnapshot();
182
+ const safeRoot = sanitizeRow ? [resolvedEndpoint, 'safe-select', sanitizerId(sanitizeRow), auth.uid, getAuthSessionRevision()] as const : undefined;
170
183
  const uid = useId();
171
184
  const inputDomId = `colsel-input-${uid}`;
172
185
  const listboxId = `colsel-list-${uid}`;
@@ -239,7 +252,12 @@ export function CollectionSelect<T extends { id: number | string }>({
239
252
  // Fetch only while the dropdown is open (the source fetched on focus);
240
253
  // with emptyFetch off, additionally only once there is a search term.
241
254
  const listEnabled = open && !waitingForGroup && (emptyFetch || term !== '');
242
- const query = useModelList<T>(resolvedEndpoint, wire, { enabled: listEnabled });
255
+ const query = useQuery({
256
+ queryKey: safeRoot ? [...safeRoot, wire] : [resolvedEndpoint, wire],
257
+ queryFn: async () => { const page = await mojoList<T>(resolvedEndpoint, wire); return sanitizeRow ? { ...page, rows: page.rows.map(sanitizeRow) } : page; },
258
+ placeholderData: sanitizeRow ? undefined : keepPreviousData,
259
+ enabled: listEnabled && (!sanitizeRow || auth.authenticated),
260
+ });
243
261
  // A held query (emptyFetch=false with no term, or no active group) must
244
262
  // show its empty state — keepPreviousData would otherwise keep serving
245
263
  // the previous key's rows through the placeholder.
@@ -275,7 +293,7 @@ export function CollectionSelect<T extends { id: number | string }>({
275
293
  const pickedLabel = selKey != null && picked?.key === selKey ? picked.label : null;
276
294
  let scanLabel: string | null = null;
277
295
  if (selKey != null && seedLabel == null && pickedLabel == null) {
278
- const row = findRowInListCaches(qc, resolvedEndpoint, selKey, valueField);
296
+ const row = findRowInListCaches(qc, resolvedEndpoint, selKey, valueField, safeRoot);
279
297
  if (row != null) {
280
298
  const raw = getPath(row, labelField);
281
299
  // A row without the label field stays unresolved — the one-record
@@ -284,7 +302,11 @@ export function CollectionSelect<T extends { id: number | string }>({
284
302
  }
285
303
  }
286
304
  const needFetch = selKey != null && seedLabel == null && pickedLabel == null && scanLabel == null;
287
- const oneQuery = useModel<T>(resolvedEndpoint, needFetch && selId != null ? coerceFetchId(selId) : null);
305
+ const oneQuery = useQuery({
306
+ queryKey: safeRoot ? [...safeRoot, 'one', coerceFetchId(selId ?? '')] : [resolvedEndpoint, 'one', needFetch && selId != null ? coerceFetchId(selId) : null],
307
+ queryFn: async () => { const row = await mojoGet<T>(resolvedEndpoint, coerceFetchId(selId!), { unscoped: true }); return sanitizeRow ? sanitizeRow(row) : row; },
308
+ enabled: needFetch && selId != null && (!sanitizeRow || auth.authenticated),
309
+ });
288
310
  let oneLabel: string | null = null;
289
311
  if (needFetch && oneQuery.data != null) {
290
312
  const raw = getPath(oneQuery.data, labelField);