portal-mojo 0.1.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.
Files changed (369) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +55 -0
  3. package/docs/DetailView.md +173 -0
  4. package/docs/ModelTable.md +128 -0
  5. package/docs/README.md +98 -0
  6. package/docs/admin-assistant.md +72 -0
  7. package/docs/admin-bouncer.md +120 -0
  8. package/docs/admin-cloudwatch.md +38 -0
  9. package/docs/admin-credentials.md +116 -0
  10. package/docs/admin-dashboard.md +15 -0
  11. package/docs/admin-devices-geoip.md +284 -0
  12. package/docs/admin-dns-certificates.md +122 -0
  13. package/docs/admin-dns-records.md +65 -0
  14. package/docs/admin-dns-registrar.md +33 -0
  15. package/docs/admin-dns.md +80 -0
  16. package/docs/admin-identity-users.md +103 -0
  17. package/docs/admin-incidents.md +71 -0
  18. package/docs/admin-jobs.md +426 -0
  19. package/docs/admin-members.md +112 -0
  20. package/docs/admin-messaging.md +56 -0
  21. package/docs/admin-metrics-explorer.md +63 -0
  22. package/docs/admin-monitoring.md +99 -0
  23. package/docs/admin-network-security.md +393 -0
  24. package/docs/admin-phonehub.md +27 -0
  25. package/docs/admin-push.md +36 -0
  26. package/docs/admin-rules.md +87 -0
  27. package/docs/admin-security-tickets.md +40 -0
  28. package/docs/admin-settings.md +78 -0
  29. package/docs/admin-shortlinks.md +32 -0
  30. package/docs/admin-storage.md +141 -0
  31. package/docs/auth-pages.md +212 -0
  32. package/docs/auth.md +58 -0
  33. package/docs/calendar.md +122 -0
  34. package/docs/charts.md +303 -0
  35. package/docs/client.md +206 -0
  36. package/docs/collection-multiselect.md +117 -0
  37. package/docs/collection-select.md +132 -0
  38. package/docs/combobox.md +105 -0
  39. package/docs/dataview.md +226 -0
  40. package/docs/datepicker.md +151 -0
  41. package/docs/daterange.md +170 -0
  42. package/docs/datetimepicker.md +249 -0
  43. package/docs/defineModel.md +72 -0
  44. package/docs/detail-primitives.md +174 -0
  45. package/docs/feedback.md +85 -0
  46. package/docs/file-upload-ux.md +146 -0
  47. package/docs/forms.md +435 -0
  48. package/docs/grouping-and-fmt.md +98 -0
  49. package/docs/idioms.md +112 -0
  50. package/docs/image-editor.md +101 -0
  51. package/docs/loading.md +242 -0
  52. package/docs/location-address.md +76 -0
  53. package/docs/markdown.md +203 -0
  54. package/docs/menus-and-access.md +89 -0
  55. package/docs/multiselect-dropdown.md +153 -0
  56. package/docs/params.md +73 -0
  57. package/docs/popover.md +130 -0
  58. package/docs/realtime.md +61 -0
  59. package/docs/record-feed.md +238 -0
  60. package/docs/right-panel.md +42 -0
  61. package/docs/tabs-and-form-wizard.md +60 -0
  62. package/docs/taginput.md +120 -0
  63. package/docs/timepicker.md +173 -0
  64. package/docs/timezone-select.md +147 -0
  65. package/docs/worldmap.md +302 -0
  66. package/package.json +55 -0
  67. package/src/admin/assistant/AssistantFeed.tsx +29 -0
  68. package/src/admin/assistant/AssistantPanel.tsx +65 -0
  69. package/src/admin/assistant/api.ts +17 -0
  70. package/src/admin/assistant/data.ts +63 -0
  71. package/src/admin/assistant/index.ts +21 -0
  72. package/src/admin/assistant/launchers.tsx +33 -0
  73. package/src/admin/assistant/pages.tsx +78 -0
  74. package/src/admin/assistant/streaming.ts +238 -0
  75. package/src/admin/assistant/types.ts +30 -0
  76. package/src/admin/bouncer/devices.tsx +230 -0
  77. package/src/admin/bouncer/index.ts +34 -0
  78. package/src/admin/bouncer/models.ts +199 -0
  79. package/src/admin/bouncer/signals.tsx +182 -0
  80. package/src/admin/bouncer/signatures.tsx +220 -0
  81. package/src/admin/cloudwatch/CloudWatchChart.tsx +61 -0
  82. package/src/admin/cloudwatch/CloudWatchDashboardPage.tsx +52 -0
  83. package/src/admin/cloudwatch/CloudWatchResourceDetail.tsx +33 -0
  84. package/src/admin/cloudwatch/data.ts +163 -0
  85. package/src/admin/cloudwatch/index.ts +16 -0
  86. package/src/admin/credentials/group-api-keys.tsx +433 -0
  87. package/src/admin/credentials/index.ts +4 -0
  88. package/src/admin/credentials/models.ts +244 -0
  89. package/src/admin/credentials/secret-dialog.tsx +77 -0
  90. package/src/admin/credentials/webhook-subscriptions.tsx +423 -0
  91. package/src/admin/dashboard/AdminDashboardPage.tsx +101 -0
  92. package/src/admin/dashboard/data.ts +60 -0
  93. package/src/admin/dashboard/index.ts +2 -0
  94. package/src/admin/dns/CertificateDetail.tsx +115 -0
  95. package/src/admin/dns/CertificateLifecyclePoller.tsx +141 -0
  96. package/src/admin/dns/CertificateRequestDialog.tsx +103 -0
  97. package/src/admin/dns/CertificatesPage.tsx +85 -0
  98. package/src/admin/dns/DnsRecordEditor.tsx +110 -0
  99. package/src/admin/dns/DnsRecordsPage.tsx +36 -0
  100. package/src/admin/dns/DnsRecordsPanel.tsx +71 -0
  101. package/src/admin/dns/DomainCertificatesSection.tsx +40 -0
  102. package/src/admin/dns/DomainDetail.tsx +41 -0
  103. package/src/admin/dns/DomainOnboardingDialog.tsx +27 -0
  104. package/src/admin/dns/DomainPurchaseDetail.tsx +11 -0
  105. package/src/admin/dns/DomainPurchaseWizard.tsx +112 -0
  106. package/src/admin/dns/DomainPurchasesPage.tsx +16 -0
  107. package/src/admin/dns/DomainsPage.tsx +27 -0
  108. package/src/admin/dns/ProviderCredentialsPage.tsx +304 -0
  109. package/src/admin/dns/RegistrantContactPage.tsx +42 -0
  110. package/src/admin/dns/api.ts +515 -0
  111. package/src/admin/dns/certificate-data.ts +221 -0
  112. package/src/admin/dns/data.ts +32 -0
  113. package/src/admin/dns/dns-data.ts +266 -0
  114. package/src/admin/dns/dns-integration.ts +24 -0
  115. package/src/admin/dns/domain-links.ts +37 -0
  116. package/src/admin/dns/index.ts +86 -0
  117. package/src/admin/dns/models.ts +418 -0
  118. package/src/admin/dns/purchase-data.ts +67 -0
  119. package/src/admin/dns/registrant-data.ts +52 -0
  120. package/src/admin/identity/index.ts +5 -0
  121. package/src/admin/identity/members/GroupMembersPanel.tsx +146 -0
  122. package/src/admin/identity/members/MemberDetail.tsx +226 -0
  123. package/src/admin/identity/members/MembersPage.tsx +144 -0
  124. package/src/admin/identity/members/index.ts +24 -0
  125. package/src/admin/identity/members/member-flows.tsx +132 -0
  126. package/src/admin/identity/members/models.ts +204 -0
  127. package/src/admin/identity/users/UserDetail.tsx +257 -0
  128. package/src/admin/identity/users/UsersPage.tsx +163 -0
  129. package/src/admin/identity/users/index.ts +27 -0
  130. package/src/admin/identity/users/models.ts +232 -0
  131. package/src/admin/identity/users/sections/ApiKeysSection.tsx +119 -0
  132. package/src/admin/identity/users/sections/AuditSection.tsx +190 -0
  133. package/src/admin/identity/users/sections/DevicesSection.tsx +140 -0
  134. package/src/admin/identity/users/sections/GroupsSection.tsx +64 -0
  135. package/src/admin/identity/users/sections/LoginsSection.tsx +106 -0
  136. package/src/admin/identity/users/sections/MetadataPanel.tsx +36 -0
  137. package/src/admin/identity/users/sections/NotificationsSection.tsx +117 -0
  138. package/src/admin/identity/users/sections/OAuthSection.tsx +72 -0
  139. package/src/admin/identity/users/sections/OverviewSection.tsx +109 -0
  140. package/src/admin/identity/users/sections/PermissionsSection.tsx +35 -0
  141. package/src/admin/identity/users/sections/PersonalSection.tsx +73 -0
  142. package/src/admin/identity/users/sections/ProfileSection.tsx +175 -0
  143. package/src/admin/identity/users/sections/SecuritySection.tsx +123 -0
  144. package/src/admin/identity/users/sections/actions.tsx +752 -0
  145. package/src/admin/identity/users/sections/permission-catalog.ts +138 -0
  146. package/src/admin/identity/users/sections/queries.ts +74 -0
  147. package/src/admin/identity/users/sections/shared.tsx +299 -0
  148. package/src/admin/incidents/EventDetail.tsx +43 -0
  149. package/src/admin/incidents/EventsPage.tsx +52 -0
  150. package/src/admin/incidents/IncidentDetail.tsx +68 -0
  151. package/src/admin/incidents/IncidentsPage.tsx +102 -0
  152. package/src/admin/incidents/forensics.tsx +73 -0
  153. package/src/admin/incidents/index.ts +16 -0
  154. package/src/admin/incidents/models.ts +113 -0
  155. package/src/admin/incidents/sanitize.ts +71 -0
  156. package/src/admin/index.ts +383 -0
  157. package/src/admin/jobs/JobDashboardPage.tsx +111 -0
  158. package/src/admin/jobs/JobDetail.tsx +721 -0
  159. package/src/admin/jobs/JobRunnersPage.tsx +216 -0
  160. package/src/admin/jobs/JobsTablePage.tsx +122 -0
  161. package/src/admin/jobs/RunnerDetail.tsx +576 -0
  162. package/src/admin/jobs/ScheduledTaskDetail.tsx +421 -0
  163. package/src/admin/jobs/ScheduledTasksPage.tsx +167 -0
  164. package/src/admin/jobs/columns.tsx +297 -0
  165. package/src/admin/jobs/control.ts +488 -0
  166. package/src/admin/jobs/index.ts +85 -0
  167. package/src/admin/jobs/models.ts +599 -0
  168. package/src/admin/jobs/queries.ts +272 -0
  169. package/src/admin/jobs/scheduled-task-form.tsx +493 -0
  170. package/src/admin/jobs/sections/JobChannelsPanel.tsx +142 -0
  171. package/src/admin/jobs/sections/JobOperationsSection.tsx +423 -0
  172. package/src/admin/jobs/sections/JobRunnersStrip.tsx +82 -0
  173. package/src/admin/jobs/sections/JobThroughputSection.tsx +118 -0
  174. package/src/admin/messaging/EmailDomainWizard.tsx +24 -0
  175. package/src/admin/messaging/EmailDomainsPage.tsx +17 -0
  176. package/src/admin/messaging/EmailTemplatesPage.tsx +13 -0
  177. package/src/admin/messaging/MailboxesPage.tsx +15 -0
  178. package/src/admin/messaging/PublicMessagesPage.tsx +13 -0
  179. package/src/admin/messaging/SandboxedEmailPreview.tsx +23 -0
  180. package/src/admin/messaging/SentMessagesPage.tsx +11 -0
  181. package/src/admin/messaging/api.ts +17 -0
  182. package/src/admin/messaging/data.ts +10 -0
  183. package/src/admin/messaging/index.ts +23 -0
  184. package/src/admin/messaging/models.ts +76 -0
  185. package/src/admin/messaging/push/PushPage.tsx +44 -0
  186. package/src/admin/messaging/push/api.ts +17 -0
  187. package/src/admin/messaging/push/index.ts +9 -0
  188. package/src/admin/messaging/push/models.ts +43 -0
  189. package/src/admin/messaging/sanitize.ts +44 -0
  190. package/src/admin/monitoring/LogInspector.tsx +160 -0
  191. package/src/admin/monitoring/LogsPage.tsx +135 -0
  192. package/src/admin/monitoring/MetricsExplorerPage.tsx +360 -0
  193. package/src/admin/monitoring/MetricsPermissionsPage.tsx +166 -0
  194. package/src/admin/monitoring/MetricsSourcePicker.tsx +136 -0
  195. package/src/admin/monitoring/index.ts +55 -0
  196. package/src/admin/monitoring/metrics-explorer-client.ts +229 -0
  197. package/src/admin/monitoring/metrics-explorer-data.ts +158 -0
  198. package/src/admin/monitoring/models.ts +155 -0
  199. package/src/admin/network/BlockedIPsPage.tsx +194 -0
  200. package/src/admin/network/BlockedIpDetail.tsx +262 -0
  201. package/src/admin/network/FirewallLogPage.tsx +115 -0
  202. package/src/admin/network/IPSetDetail.tsx +280 -0
  203. package/src/admin/network/IPSetEditor.tsx +191 -0
  204. package/src/admin/network/IPSetsPage.tsx +177 -0
  205. package/src/admin/network/geofence/BlocksTab.tsx +289 -0
  206. package/src/admin/network/geofence/ExemptionsTab.tsx +349 -0
  207. package/src/admin/network/geofence/GeofencingPage.tsx +119 -0
  208. package/src/admin/network/geofence/PostureHeader.tsx +184 -0
  209. package/src/admin/network/geofence/RuleEditor.tsx +176 -0
  210. package/src/admin/network/geofence/RulesTab.tsx +254 -0
  211. package/src/admin/network/geofence/SimulatorTab.tsx +281 -0
  212. package/src/admin/network/geofence/geofence-data.ts +674 -0
  213. package/src/admin/network/index.ts +68 -0
  214. package/src/admin/network/models.ts +607 -0
  215. package/src/admin/phonehub/PhoneHubPage.tsx +57 -0
  216. package/src/admin/phonehub/api.ts +47 -0
  217. package/src/admin/phonehub/data.ts +6 -0
  218. package/src/admin/phonehub/index.ts +10 -0
  219. package/src/admin/phonehub/models.ts +68 -0
  220. package/src/admin/rules/HandlerChainBuilder.tsx +51 -0
  221. package/src/admin/rules/RuleSetDetailPage.tsx +33 -0
  222. package/src/admin/rules/RuleSetsPage.tsx +33 -0
  223. package/src/admin/rules/editors.tsx +54 -0
  224. package/src/admin/rules/handler-dsl.ts +213 -0
  225. package/src/admin/rules/index.ts +13 -0
  226. package/src/admin/rules/models.ts +112 -0
  227. package/src/admin/security/devices/LoginEventDetail.tsx +123 -0
  228. package/src/admin/security/devices/LoginLocationMap.tsx +321 -0
  229. package/src/admin/security/devices/LoginLocationsPage.tsx +162 -0
  230. package/src/admin/security/devices/UserDeviceDetail.tsx +546 -0
  231. package/src/admin/security/devices/UserDeviceLocationDetail.tsx +272 -0
  232. package/src/admin/security/devices/UserDevicesPage.tsx +84 -0
  233. package/src/admin/security/devices/index.ts +40 -0
  234. package/src/admin/security/devices/models.ts +470 -0
  235. package/src/admin/security/geoip/GeoIpCachePage.tsx +135 -0
  236. package/src/admin/security/geoip/GeoIpDossier.tsx +804 -0
  237. package/src/admin/security/geoip/geoip-forms.ts +102 -0
  238. package/src/admin/security/geoip/index.ts +27 -0
  239. package/src/admin/security/geoip/models.ts +397 -0
  240. package/src/admin/security/index.ts +31 -0
  241. package/src/admin/security/models.ts +137 -0
  242. package/src/admin/security/tickets.tsx +562 -0
  243. package/src/admin/security-permissions.ts +5 -0
  244. package/src/admin/settings/SettingDetail.tsx +57 -0
  245. package/src/admin/settings/SettingEditor.tsx +157 -0
  246. package/src/admin/settings/SettingsPage.tsx +72 -0
  247. package/src/admin/settings/index.ts +19 -0
  248. package/src/admin/settings/model.ts +143 -0
  249. package/src/admin/shortlinks/ShortlinkDetail.tsx +55 -0
  250. package/src/admin/shortlinks/ShortlinkHistoryPage.tsx +18 -0
  251. package/src/admin/shortlinks/ShortlinksPage.tsx +59 -0
  252. package/src/admin/shortlinks/index.ts +18 -0
  253. package/src/admin/shortlinks/models.ts +246 -0
  254. package/src/admin/storage/BackendsPage.tsx +43 -0
  255. package/src/admin/storage/BucketDetail.tsx +77 -0
  256. package/src/admin/storage/BucketsPage.tsx +64 -0
  257. package/src/admin/storage/FileManagerDetail.tsx +36 -0
  258. package/src/admin/storage/FilePreview.tsx +54 -0
  259. package/src/admin/storage/FileUploadSurface.tsx +250 -0
  260. package/src/admin/storage/FileView.tsx +109 -0
  261. package/src/admin/storage/FilesPage.tsx +53 -0
  262. package/src/admin/storage/file-renditions.ts +99 -0
  263. package/src/admin/storage/index.ts +31 -0
  264. package/src/admin/storage/models.ts +585 -0
  265. package/src/admin/storage/storage-dialogs.tsx +80 -0
  266. package/src/charts/CircularProgress.tsx +281 -0
  267. package/src/charts/KPIStrip.tsx +162 -0
  268. package/src/charts/KPITile.tsx +125 -0
  269. package/src/charts/MetricsChart.tsx +459 -0
  270. package/src/charts/MetricsMiniWidget.tsx +588 -0
  271. package/src/charts/MiniChart.tsx +283 -0
  272. package/src/charts/PieChart.tsx +354 -0
  273. package/src/charts/SeriesChart.tsx +378 -0
  274. package/src/charts/chart-dialogs.tsx +149 -0
  275. package/src/charts/exportChart.ts +144 -0
  276. package/src/charts/index.ts +28 -0
  277. package/src/charts/pie-math.ts +127 -0
  278. package/src/charts/stats.ts +265 -0
  279. package/src/charts/worldmap/WorldMap.tsx +649 -0
  280. package/src/charts/worldmap/countryCentroids.ts +304 -0
  281. package/src/charts/worldmap/geo.ts +293 -0
  282. package/src/charts/worldmap/use-world-land.ts +47 -0
  283. package/src/charts/worldmap/world-land.ts +18 -0
  284. package/src/charts/worldmap/worldmap-data.ts +285 -0
  285. package/src/client/auth.ts +737 -0
  286. package/src/client/client.ts +268 -0
  287. package/src/client/duid.ts +31 -0
  288. package/src/client/errors.ts +33 -0
  289. package/src/client/group-context.ts +28 -0
  290. package/src/client/group.tsx +126 -0
  291. package/src/client/hooks.ts +50 -0
  292. package/src/client/index.ts +37 -0
  293. package/src/client/jwt.ts +75 -0
  294. package/src/client/location.ts +285 -0
  295. package/src/client/lookups.ts +41 -0
  296. package/src/client/markdown-fallback.tsx +115 -0
  297. package/src/client/markdown-parse.ts +386 -0
  298. package/src/client/markdown-sanitize.ts +217 -0
  299. package/src/client/markdown.ts +122 -0
  300. package/src/client/me.ts +188 -0
  301. package/src/client/mock.ts +9984 -0
  302. package/src/client/model.ts +227 -0
  303. package/src/client/params.ts +292 -0
  304. package/src/client/realtime-mock.ts +221 -0
  305. package/src/client/realtime.ts +561 -0
  306. package/src/client/record-feed.ts +329 -0
  307. package/src/client/safe-export.ts +71 -0
  308. package/src/client/types.ts +278 -0
  309. package/src/client/upload.ts +421 -0
  310. package/src/ui/AddressField.tsx +273 -0
  311. package/src/ui/AttachmentQueue.tsx +92 -0
  312. package/src/ui/CollectionMultiSelect.tsx +622 -0
  313. package/src/ui/CollectionSelect.tsx +517 -0
  314. package/src/ui/ComboBox.tsx +336 -0
  315. package/src/ui/DetailView.tsx +438 -0
  316. package/src/ui/FileDrop.tsx +194 -0
  317. package/src/ui/FileField.tsx +369 -0
  318. package/src/ui/FilterBar.tsx +269 -0
  319. package/src/ui/FormFields.tsx +77 -0
  320. package/src/ui/FormView.tsx +360 -0
  321. package/src/ui/FormWizard.tsx +336 -0
  322. package/src/ui/GroupSwitcher.tsx +202 -0
  323. package/src/ui/Guarded.tsx +16 -0
  324. package/src/ui/MarkdownView.tsx +89 -0
  325. package/src/ui/ModelTable.tsx +848 -0
  326. package/src/ui/MultiSelectDropdown.tsx +390 -0
  327. package/src/ui/Popover.tsx +204 -0
  328. package/src/ui/RecordFeed.tsx +627 -0
  329. package/src/ui/RequiresGroup.tsx +32 -0
  330. package/src/ui/RightPanel.tsx +107 -0
  331. package/src/ui/SidebarNav.tsx +330 -0
  332. package/src/ui/Tabs.tsx +161 -0
  333. package/src/ui/TagInput.tsx +381 -0
  334. package/src/ui/ThemeProvider.tsx +52 -0
  335. package/src/ui/UploadQueue.tsx +603 -0
  336. package/src/ui/armed-button.tsx +91 -0
  337. package/src/ui/date/Calendar.tsx +498 -0
  338. package/src/ui/date/DatePicker.tsx +289 -0
  339. package/src/ui/date/DateRangePicker.tsx +280 -0
  340. package/src/ui/date/DateTimePicker.tsx +587 -0
  341. package/src/ui/date/PresetRail.tsx +165 -0
  342. package/src/ui/date/TimePicker.tsx +624 -0
  343. package/src/ui/date/TimezoneSelect.tsx +267 -0
  344. package/src/ui/date/fns.ts +679 -0
  345. package/src/ui/detail/DataView.tsx +692 -0
  346. package/src/ui/detail/FlowStrip.tsx +80 -0
  347. package/src/ui/detail/KnownFieldsCard.tsx +121 -0
  348. package/src/ui/detail/MetadataSection.tsx +219 -0
  349. package/src/ui/detail/StackTraceView.tsx +150 -0
  350. package/src/ui/detail/StatusPanel.tsx +73 -0
  351. package/src/ui/detail/Timeline.tsx +52 -0
  352. package/src/ui/detail/index.ts +12 -0
  353. package/src/ui/field-registry.tsx +428 -0
  354. package/src/ui/field-wire.ts +322 -0
  355. package/src/ui/form-autosave.ts +583 -0
  356. package/src/ui/format.ts +444 -0
  357. package/src/ui/grouping.ts +150 -0
  358. package/src/ui/image-editor/ImageEditor.tsx +484 -0
  359. package/src/ui/image-editor/canvas.ts +254 -0
  360. package/src/ui/image-editor/index.ts +3 -0
  361. package/src/ui/image-editor/math.ts +416 -0
  362. package/src/ui/index.ts +58 -0
  363. package/src/ui/loading.tsx +604 -0
  364. package/src/ui/menu-registry.ts +213 -0
  365. package/src/ui/modal.tsx +297 -0
  366. package/src/ui/password.tsx +302 -0
  367. package/src/ui/schema-form-core.tsx +315 -0
  368. package/src/ui/toast.tsx +219 -0
  369. package/src/ui/ui.tsx +45 -0
@@ -0,0 +1,272 @@
1
+ // Admin jobs — TanStack query hooks over the control plane and the job model
2
+ // endpoints.
3
+ //
4
+ // EVERY hook is `enabled`-gated on the matching `useCan`. A viewer-only
5
+ // operator must issue zero denied requests: a permission-disabled query never
6
+ // fires at all, rather than firing and catching a 403. `useCan` is fail-closed
7
+ // while `me` loads, so the first render of an admin page never races a gate.
8
+ import { useMemo } from 'react';
9
+ import { useQuery } from '@tanstack/react-query';
10
+ import { mojoList, useCan, type Params } from '../../client';
11
+ import {
12
+ fetchChannels,
13
+ fetchJobStats,
14
+ fetchJobsConfig,
15
+ fetchQueueSizes,
16
+ fetchRunnerSysinfo,
17
+ fetchRunners,
18
+ } from './control';
19
+ import {
20
+ JOBS_MANAGE_PERMS,
21
+ JOBS_VIEW_PERMS,
22
+ JobEventModel,
23
+ JobLogModel,
24
+ JobModel,
25
+ type JobLogRow,
26
+ type JobRow,
27
+ type JobTimelineEntry,
28
+ } from './models';
29
+
30
+ /** One root (`['jobs']`) so a mutation can invalidate the whole domain. */
31
+ export const jobsKeys = {
32
+ root: ['jobs'] as const,
33
+ stats: () => ['jobs', 'stats'] as const,
34
+ runners: (channel?: string) => ['jobs', 'runners', channel ?? null] as const,
35
+ channels: () => ['jobs', 'channels'] as const,
36
+ queueSizes: () => ['jobs', 'queue-sizes'] as const,
37
+ config: () => ['jobs', 'config'] as const,
38
+ sysinfo: (runnerId: string) => ['jobs', 'sysinfo', runnerId] as const,
39
+ timeline: (jobId: string) => [JobEventModel.endpoint, 'timeline', jobId] as const,
40
+ jobLogs: (jobId: string) => [JobLogModel.endpoint, 'job', jobId] as const,
41
+ runnerLogs: (runnerId: string) => [JobLogModel.endpoint, 'runner', runnerId] as const,
42
+ };
43
+
44
+ /** Dashboard tick. Fast enough to be an alerting signal, slow enough to be free. */
45
+ const DASHBOARD_INTERVAL_MS = 60_000;
46
+
47
+ /**
48
+ * `GET /api/jobs/stats` — the dashboard's single source of truth. It replaces
49
+ * `/api/jobs/health`, which raises a KeyError server-side and is never called.
50
+ */
51
+ export function useJobStats() {
52
+ const { can } = useCan(JOBS_VIEW_PERMS);
53
+ return useQuery({
54
+ queryKey: jobsKeys.stats(),
55
+ queryFn: fetchJobStats,
56
+ enabled: can,
57
+ refetchInterval: DASHBOARD_INTERVAL_MS,
58
+ });
59
+ }
60
+
61
+ /**
62
+ * `GET /api/jobs/runners` — the COMPLETE fleet list. The endpoint has no
63
+ * paging/sort/search contract, so callers sort and filter client-side.
64
+ */
65
+ export function useRunners(channel?: string) {
66
+ const { can } = useCan(JOBS_VIEW_PERMS);
67
+ return useQuery({
68
+ queryKey: jobsKeys.runners(channel),
69
+ queryFn: () => fetchRunners(channel),
70
+ enabled: can,
71
+ refetchInterval: DASHBOARD_INTERVAL_MS,
72
+ });
73
+ }
74
+
75
+ /** `GET /api/jobs/control/channels` — channels discovered from Redis streams. */
76
+ export function useDiscoveredChannels() {
77
+ const { can } = useCan(JOBS_VIEW_PERMS);
78
+ return useQuery({
79
+ queryKey: jobsKeys.channels(),
80
+ queryFn: fetchChannels,
81
+ enabled: can,
82
+ });
83
+ }
84
+
85
+ export interface ChannelOptions {
86
+ /** Deduped, sorted channel names. Empty means nothing is configured. */
87
+ channels: string[];
88
+ options: { value: string; label: string }[];
89
+ isPending: boolean;
90
+ /** True once both sources answered and neither offered a channel. */
91
+ isEmpty: boolean;
92
+ }
93
+
94
+ /**
95
+ * The union of the two channel sources, because neither is complete on its own:
96
+ * `stats.channels` is keyed by `settings.JOBS_CHANNELS` (declared but maybe
97
+ * never used), while `control/channels` scans Redis for streams that actually
98
+ * exist (including ones nobody declared). An operation targeting a channel
99
+ * should be offered for either.
100
+ */
101
+ export function useChannelOptions(): ChannelOptions {
102
+ const stats = useJobStats();
103
+ const discovered = useDiscoveredChannels();
104
+ const channels = useMemo(() => {
105
+ const names = new Set<string>();
106
+ for (const name of Object.keys(stats.data?.channels ?? {})) if (name) names.add(name);
107
+ for (const name of discovered.data ?? []) if (name) names.add(name);
108
+ return [...names].sort((a, b) => a.localeCompare(b));
109
+ }, [stats.data, discovered.data]);
110
+ const isPending = stats.isPending || discovered.isPending;
111
+ return {
112
+ channels,
113
+ options: channels.map((value) => ({ value, label: value })),
114
+ isPending,
115
+ isEmpty: !isPending && channels.length === 0,
116
+ };
117
+ }
118
+
119
+ /** `GET /api/jobs/control/queue-sizes` — Redis + DB counts per channel. */
120
+ export function useQueueSizes() {
121
+ const { can } = useCan(JOBS_VIEW_PERMS);
122
+ return useQuery({
123
+ queryKey: jobsKeys.queueSizes(),
124
+ queryFn: fetchQueueSizes,
125
+ enabled: can,
126
+ });
127
+ }
128
+
129
+ /**
130
+ * `GET /api/jobs/control/config` — MANAGE-gated, unlike every other read in
131
+ * this domain. A view-only operator never issues this request.
132
+ */
133
+ export function useJobsConfig() {
134
+ const { can } = useCan(JOBS_MANAGE_PERMS);
135
+ return useQuery({
136
+ queryKey: jobsKeys.config(),
137
+ queryFn: fetchJobsConfig,
138
+ enabled: can,
139
+ });
140
+ }
141
+
142
+ /**
143
+ * `GET /api/jobs/runners/sysinfo/<id>` — a redis broadcast-execute that blocks
144
+ * the request thread for up to `timeout` seconds waiting for the runner to
145
+ * answer. NEVER polled: it fetches once when the caller enables it, and again
146
+ * only on an explicit refetch. Results stay fresh indefinitely so switching
147
+ * sections does not re-broadcast.
148
+ */
149
+ export function useRunnerSysinfo(runnerId: string | null, opts: { enabled?: boolean; timeout?: number } = {}) {
150
+ const { can } = useCan(JOBS_VIEW_PERMS);
151
+ return useQuery({
152
+ queryKey: jobsKeys.sysinfo(runnerId ?? ''),
153
+ queryFn: () => fetchRunnerSysinfo(runnerId!, opts.timeout ?? 5),
154
+ enabled: can && runnerId != null && opts.enabled !== false,
155
+ refetchInterval: false,
156
+ refetchOnMount: false,
157
+ staleTime: Infinity,
158
+ // A runner that never answers is a 404 envelope; each retry costs
159
+ // another full timeout window on the server.
160
+ retry: false,
161
+ });
162
+ }
163
+
164
+ const TIMELINE_SIZE = 200;
165
+
166
+ /**
167
+ * `GET /api/jobs/event?job=<id>&graph=timeline` — the lifecycle trail.
168
+ *
169
+ * Job carries NO `recent_events` in any graph (only inside the `get_status`
170
+ * action payload), so this endpoint IS the timeline. `job` is the relation
171
+ * name; `job_id` would be silently dropped by `build_rest_filters` and return
172
+ * every event in the system. Sorted ASCENDING because a lifecycle reads
173
+ * forward, against the model's own `-at` default.
174
+ */
175
+ export function useJobTimeline(jobId: string | null) {
176
+ const { can } = useCan(JOBS_VIEW_PERMS);
177
+ return useQuery({
178
+ queryKey: jobsKeys.timeline(jobId ?? ''),
179
+ queryFn: () => mojoList<JobTimelineEntry>(JobEventModel.endpoint, {
180
+ job: jobId!,
181
+ graph: 'timeline',
182
+ sort: 'at',
183
+ size: TIMELINE_SIZE,
184
+ }),
185
+ enabled: can && jobId != null,
186
+ });
187
+ }
188
+
189
+ const LOG_SIZE = 200;
190
+
191
+ /**
192
+ * `GET /api/jobs/logs?job=<id>` — one job's log lines, newest first.
193
+ * `job=` (the relation) is the ONLY filter that works; `job_id=` is dropped.
194
+ */
195
+ export function useJobLogs(jobId: string | null) {
196
+ const { can } = useCan(JOBS_VIEW_PERMS);
197
+ return useQuery({
198
+ queryKey: jobsKeys.jobLogs(jobId ?? ''),
199
+ queryFn: () => mojoList<JobLogRow>(JobLogModel.endpoint, {
200
+ job: jobId!,
201
+ sort: '-created',
202
+ size: LOG_SIZE,
203
+ }),
204
+ enabled: can && jobId != null,
205
+ });
206
+ }
207
+
208
+ /** Runner detail polls its live panes on this tick; sysinfo never joins them. */
209
+ const RUNNER_DETAIL_INTERVAL_MS = 15_000;
210
+
211
+ /**
212
+ * A runner's currently-running jobs. `runner_id` IS a real Job field, so
213
+ * unlike the JobLog case this filter genuinely works.
214
+ */
215
+ export function useRunnerActiveJobs(runnerId: string | null) {
216
+ const { can } = useCan(JOBS_VIEW_PERMS);
217
+ const params: Params = { runner_id: runnerId ?? '', status: 'running', sort: '-started_at', size: 25 };
218
+ return useQuery({
219
+ queryKey: [JobModel.endpoint, 'runner-active', runnerId ?? ''],
220
+ queryFn: () => mojoList<JobRow>(JobModel.endpoint, params),
221
+ enabled: can && runnerId != null,
222
+ refetchInterval: RUNNER_DETAIL_INTERVAL_MS,
223
+ });
224
+ }
225
+
226
+ /** A runner's most recent completions. */
227
+ export function useRunnerJobHistory(runnerId: string | null) {
228
+ const { can } = useCan(JOBS_VIEW_PERMS);
229
+ const params: Params = { runner_id: runnerId ?? '', status: 'completed', sort: '-created', size: 25 };
230
+ return useQuery({
231
+ queryKey: [JobModel.endpoint, 'runner-history', runnerId ?? ''],
232
+ queryFn: () => mojoList<JobRow>(JobModel.endpoint, params),
233
+ enabled: can && runnerId != null,
234
+ });
235
+ }
236
+
237
+ /** How many of a runner's recent jobs feed the two-step log lookup. */
238
+ const RUNNER_LOG_JOB_WINDOW = 25;
239
+
240
+ /**
241
+ * A runner's recent log lines, in TWO steps — because JobLog has no
242
+ * `runner_id` field at all, and `build_rest_filters` silently drops unknown
243
+ * keys, so `?runner_id=` returns the whole unfiltered log table (web-mojo
244
+ * shipped exactly that and presented it as this runner's logs).
245
+ *
246
+ * 1. the runner's most recent jobs → `/api/jobs/job?runner_id=&sort=-created`
247
+ * 2. their log lines → `/api/jobs/logs?job__in=<ids>`
248
+ *
249
+ * No jobs means no logs, and the query resolves empty without a second call.
250
+ */
251
+ export function useRunnerJobLogs(runnerId: string | null) {
252
+ const { can } = useCan(JOBS_VIEW_PERMS);
253
+ return useQuery({
254
+ queryKey: jobsKeys.runnerLogs(runnerId ?? ''),
255
+ queryFn: async () => {
256
+ const jobParams: Params = {
257
+ runner_id: runnerId!,
258
+ sort: '-created',
259
+ size: RUNNER_LOG_JOB_WINDOW,
260
+ };
261
+ const jobs = await mojoList<JobRow>(JobModel.endpoint, jobParams);
262
+ const ids = jobs.rows.map((row) => row.id).filter(Boolean);
263
+ if (ids.length === 0) return { rows: [] as JobLogRow[], count: 0, start: 0, size: 0 };
264
+ return mojoList<JobLogRow>(JobLogModel.endpoint, {
265
+ job__in: ids.join(','),
266
+ sort: '-created',
267
+ size: LOG_SIZE,
268
+ });
269
+ },
270
+ enabled: can && runnerId != null,
271
+ });
272
+ }