@kingsleyweb/bun-ui 0.0.0-stage → 3.0.0-rc.2

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 (240) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +80 -2
  3. package/app/docs/App.tsx +72 -0
  4. package/app/docs/api/client.ts +303 -0
  5. package/app/docs/api/contract.ts +24 -0
  6. package/app/docs/api/docs.ts +38 -0
  7. package/app/docs/api/errors.ts +144 -0
  8. package/app/docs/boot.tsx +58 -0
  9. package/app/docs/components/Badge.tsx +65 -0
  10. package/app/docs/components/Button.tsx +37 -0
  11. package/app/docs/components/Card.tsx +41 -0
  12. package/app/docs/components/ConfirmDialog.tsx +173 -0
  13. package/app/docs/components/CopyButton.tsx +68 -0
  14. package/app/docs/components/Dialog.tsx +230 -0
  15. package/app/docs/components/EmptyState.tsx +34 -0
  16. package/app/docs/components/ErrorView.tsx +70 -0
  17. package/app/docs/components/Field.tsx +93 -0
  18. package/app/docs/components/JsonEditor.tsx +151 -0
  19. package/app/docs/components/JsonView.tsx +338 -0
  20. package/app/docs/components/KeyValue.tsx +63 -0
  21. package/app/docs/components/ProblemBanner.tsx +116 -0
  22. package/app/docs/components/ScreenErrorBoundary.tsx +141 -0
  23. package/app/docs/components/Spinner.tsx +33 -0
  24. package/app/docs/components/Table.tsx +33 -0
  25. package/app/docs/components/Tabs.tsx +226 -0
  26. package/app/docs/components/classNames.ts +4 -0
  27. package/app/docs/components/components.css +691 -0
  28. package/app/docs/components/copy.ts +89 -0
  29. package/app/docs/components/fieldContext.ts +29 -0
  30. package/app/docs/components/inputValues.ts +142 -0
  31. package/app/docs/components/inputs.tsx +387 -0
  32. package/app/docs/components/jsonParse.ts +338 -0
  33. package/app/docs/components/listKeys.ts +30 -0
  34. package/app/docs/config.ts +86 -0
  35. package/app/docs/context.ts +33 -0
  36. package/app/docs/embed.ts +74 -0
  37. package/app/docs/env.d.ts +2 -0
  38. package/app/docs/format.ts +9 -0
  39. package/app/docs/hooks/useUrlTab.ts +16 -0
  40. package/app/docs/main.tsx +11 -0
  41. package/app/docs/queryClient.ts +54 -0
  42. package/app/docs/router.tsx +194 -0
  43. package/app/docs/routing.ts +194 -0
  44. package/app/docs/screens/docs/document.ts +27 -0
  45. package/app/docs/screens/docs/http/OperationView.tsx +407 -0
  46. package/app/docs/screens/docs/http/Sidebar.tsx +190 -0
  47. package/app/docs/screens/docs/http/TryItPanel.tsx +603 -0
  48. package/app/docs/screens/docs/http/http.css +405 -0
  49. package/app/docs/screens/docs/http/index.tsx +395 -0
  50. package/app/docs/screens/docs/http/model.ts +604 -0
  51. package/app/docs/screens/docs/http/tryIt.ts +455 -0
  52. package/app/docs/screens/docs/permissions.ts +6 -0
  53. package/app/docs/screens/docs/schema/Prose.tsx +43 -0
  54. package/app/docs/screens/docs/schema/SchemaTree.tsx +319 -0
  55. package/app/docs/screens/docs/schema/index.ts +30 -0
  56. package/app/docs/screens/docs/schema/resolve.ts +466 -0
  57. package/app/docs/screens/docs/schema/schema.css +137 -0
  58. package/app/docs/screens/docs/schema/skeleton.ts +92 -0
  59. package/app/docs/screens/docs/viewer.tsx +154 -0
  60. package/app/docs/screens/docs/ws/InlineText.tsx +25 -0
  61. package/app/docs/screens/docs/ws/PayloadSchema.tsx +39 -0
  62. package/app/docs/screens/docs/ws/format.ts +82 -0
  63. package/app/docs/screens/docs/ws/index.tsx +286 -0
  64. package/app/docs/screens/docs/ws/items.tsx +786 -0
  65. package/app/docs/screens/docs/ws/model.ts +1178 -0
  66. package/app/docs/screens/docs/ws/panels.tsx +440 -0
  67. package/app/docs/screens/docs/ws/ws.css +231 -0
  68. package/app/docs/shortcuts.ts +94 -0
  69. package/app/docs/styles/base.css +847 -0
  70. package/app/docs/styles/tokens.css +143 -0
  71. package/app/docs/theme.ts +56 -0
  72. package/app/kit/unauthorized.ts +222 -0
  73. package/dist/docs/assets/main-0rchxtv5.js +18 -0
  74. package/dist/docs/assets/main-0rchxtv5.js.map +99 -0
  75. package/dist/docs/assets/main-cqgyvs86.css +1 -0
  76. package/dist/docs/manifest.json +26 -0
  77. package/dist/jobs/assets/AddFlowDialog-h6xkgx9b.js +6 -0
  78. package/dist/jobs/assets/AddFlowDialog-h6xkgx9b.js.map +11 -0
  79. package/dist/jobs/assets/AddFlowDialog-n7gtc84v.css +1 -0
  80. package/dist/jobs/assets/added-cbwsxv3e.js +4 -0
  81. package/dist/jobs/assets/added-cbwsxv3e.js.map +11 -0
  82. package/dist/jobs/assets/bodies-yyw5hmrp.js +6 -0
  83. package/dist/jobs/assets/bodies-yyw5hmrp.js.map +10 -0
  84. package/dist/jobs/assets/channel-tgkvqm8y.js +4 -0
  85. package/dist/jobs/assets/channel-tgkvqm8y.js.map +10 -0
  86. package/dist/jobs/assets/channelChecks-s4yz4yhw.js +4 -0
  87. package/dist/jobs/assets/channelChecks-s4yz4yhw.js.map +10 -0
  88. package/dist/jobs/assets/index-02427smp.css +1 -0
  89. package/dist/jobs/assets/index-09z2cjft.js +4 -0
  90. package/dist/jobs/assets/index-09z2cjft.js.map +16 -0
  91. package/dist/jobs/assets/index-18bn0gvy.css +1 -0
  92. package/dist/jobs/assets/index-47pxmyxn.js +4 -0
  93. package/dist/jobs/assets/index-47pxmyxn.js.map +10 -0
  94. package/dist/jobs/assets/index-4e55z986.js +4 -0
  95. package/dist/jobs/assets/index-4e55z986.js.map +11 -0
  96. package/dist/jobs/assets/index-5c933424.js +4 -0
  97. package/dist/jobs/assets/index-5c933424.js.map +18 -0
  98. package/dist/jobs/assets/index-5kbyqspm.css +1 -0
  99. package/dist/jobs/assets/index-5ng37hk6.css +1 -0
  100. package/dist/jobs/assets/index-5w8w7fvc.css +1 -0
  101. package/dist/jobs/assets/index-71qs5qyx.css +1 -0
  102. package/dist/jobs/assets/index-btpg4r8b.css +1 -0
  103. package/dist/jobs/assets/index-d5r5n0mm.js +4 -0
  104. package/dist/jobs/assets/index-d5r5n0mm.js.map +23 -0
  105. package/dist/jobs/assets/index-e0yy2skq.css +1 -0
  106. package/dist/jobs/assets/index-hs60b25v.js +10 -0
  107. package/dist/jobs/assets/index-hs60b25v.js.map +62 -0
  108. package/dist/jobs/assets/index-k2smrr70.js +4 -0
  109. package/dist/jobs/assets/index-k2smrr70.js.map +9 -0
  110. package/dist/jobs/assets/index-q24jw3w9.js +4 -0
  111. package/dist/jobs/assets/index-q24jw3w9.js.map +32 -0
  112. package/dist/jobs/assets/index-qfb80xen.css +1 -0
  113. package/dist/jobs/assets/index-rtynx6wj.js +4 -0
  114. package/dist/jobs/assets/index-rtynx6wj.js.map +10 -0
  115. package/dist/jobs/assets/live-6k7s6358.js +4 -0
  116. package/dist/jobs/assets/live-6k7s6358.js.map +10 -0
  117. package/dist/jobs/assets/main-0jmeh6mq.js +6 -0
  118. package/dist/jobs/assets/main-0jmeh6mq.js.map +12 -0
  119. package/dist/jobs/assets/main-3yj6bv90.js +5 -0
  120. package/dist/jobs/assets/main-3yj6bv90.js.map +10 -0
  121. package/dist/jobs/assets/main-4g69fn3x.js +5 -0
  122. package/dist/jobs/assets/main-4g69fn3x.js.map +10 -0
  123. package/dist/jobs/assets/main-4gte3eqq.js +5 -0
  124. package/dist/jobs/assets/main-4gte3eqq.js.map +10 -0
  125. package/dist/jobs/assets/main-4j1fcevh.js +5 -0
  126. package/dist/jobs/assets/main-4j1fcevh.js.map +10 -0
  127. package/dist/jobs/assets/main-54qwkz28.css +1 -0
  128. package/dist/jobs/assets/main-5grjvsy7.js +5 -0
  129. package/dist/jobs/assets/main-5grjvsy7.js.map +11 -0
  130. package/dist/jobs/assets/main-5zbx60gq.js +5 -0
  131. package/dist/jobs/assets/main-5zbx60gq.js.map +11 -0
  132. package/dist/jobs/assets/main-9prgjhg4.js +5 -0
  133. package/dist/jobs/assets/main-9prgjhg4.js.map +10 -0
  134. package/dist/jobs/assets/main-a250ffk7.js +17 -0
  135. package/dist/jobs/assets/main-a250ffk7.js.map +115 -0
  136. package/dist/jobs/assets/main-d6ge7w08.js +5 -0
  137. package/dist/jobs/assets/main-d6ge7w08.js.map +10 -0
  138. package/dist/jobs/assets/main-gngzht43.js +5 -0
  139. package/dist/jobs/assets/main-gngzht43.js.map +10 -0
  140. package/dist/jobs/assets/main-hg0hrg8j.js +5 -0
  141. package/dist/jobs/assets/main-hg0hrg8j.js.map +10 -0
  142. package/dist/jobs/assets/main-hm1ng3yg.js +5 -0
  143. package/dist/jobs/assets/main-hm1ng3yg.js.map +12 -0
  144. package/dist/jobs/assets/main-jkvd92z9.js +5 -0
  145. package/dist/jobs/assets/main-jkvd92z9.js.map +10 -0
  146. package/dist/jobs/assets/main-jv5xqd13.js +5 -0
  147. package/dist/jobs/assets/main-jv5xqd13.js.map +21 -0
  148. package/dist/jobs/assets/main-jxre7w93.js +5 -0
  149. package/dist/jobs/assets/main-jxre7w93.js.map +23 -0
  150. package/dist/jobs/assets/main-kwmqkjfx.js +5 -0
  151. package/dist/jobs/assets/main-kwmqkjfx.js.map +11 -0
  152. package/dist/jobs/assets/main-n9cf6bxm.js +5 -0
  153. package/dist/jobs/assets/main-n9cf6bxm.js.map +11 -0
  154. package/dist/jobs/assets/main-pegp0ygb.js +5 -0
  155. package/dist/jobs/assets/main-pegp0ygb.js.map +10 -0
  156. package/dist/jobs/assets/main-qr573853.js +5 -0
  157. package/dist/jobs/assets/main-qr573853.js.map +12 -0
  158. package/dist/jobs/assets/main-rq97rdr4.js +4 -0
  159. package/dist/jobs/assets/main-rq97rdr4.js.map +22 -0
  160. package/dist/jobs/assets/main-szjdmydc.js +5 -0
  161. package/dist/jobs/assets/main-szjdmydc.js.map +13 -0
  162. package/dist/jobs/assets/main-tnbhrm9n.js +7 -0
  163. package/dist/jobs/assets/main-tnbhrm9n.js.map +11 -0
  164. package/dist/jobs/assets/main-ts19j4n0.js +5 -0
  165. package/dist/jobs/assets/main-ts19j4n0.js.map +25 -0
  166. package/dist/jobs/assets/main-w93x1dzs.js +5 -0
  167. package/dist/jobs/assets/main-w93x1dzs.js.map +10 -0
  168. package/dist/jobs/assets/sections-0ksdxs8m.css +1 -0
  169. package/dist/jobs/assets/sections-0y7w4fb4.js +4 -0
  170. package/dist/jobs/assets/sections-0y7w4fb4.js.map +10 -0
  171. package/dist/jobs/assets/sse-mn0t0fh7.js +5 -0
  172. package/dist/jobs/assets/sse-mn0t0fh7.js.map +11 -0
  173. package/dist/jobs/assets/sseChecks-t7tcm7ra.js +4 -0
  174. package/dist/jobs/assets/sseChecks-t7tcm7ra.js.map +10 -0
  175. package/dist/jobs/assets/validation-ysjfy072.js +4 -0
  176. package/dist/jobs/assets/validation-ysjfy072.js.map +9 -0
  177. package/dist/jobs/manifest.json +511 -0
  178. package/dts/docs/assets.d.ts +144 -0
  179. package/dts/docs/assets.d.ts.map +1 -0
  180. package/dts/docs/browser/config.d.ts +61 -0
  181. package/dts/docs/browser/config.d.ts.map +1 -0
  182. package/dts/docs/browser/index.d.ts +12 -0
  183. package/dts/docs/browser/index.d.ts.map +1 -0
  184. package/dts/docs/docsUi.d.ts +42 -0
  185. package/dts/docs/docsUi.d.ts.map +1 -0
  186. package/dts/docs/hosted.d.ts +22 -0
  187. package/dts/docs/hosted.d.ts.map +1 -0
  188. package/dts/docs/index.d.ts +18 -0
  189. package/dts/docs/index.d.ts.map +1 -0
  190. package/dts/docs/shell.d.ts +57 -0
  191. package/dts/docs/shell.d.ts.map +1 -0
  192. package/dts/docs/types.d.ts +137 -0
  193. package/dts/docs/types.d.ts.map +1 -0
  194. package/dts/index.d.ts +21 -0
  195. package/dts/index.d.ts.map +1 -0
  196. package/dts/jobs/assets.d.ts +157 -0
  197. package/dts/jobs/assets.d.ts.map +1 -0
  198. package/dts/jobs/browser/config.d.ts +67 -0
  199. package/dts/jobs/browser/config.d.ts.map +1 -0
  200. package/dts/jobs/browser/index.d.ts +10 -0
  201. package/dts/jobs/browser/index.d.ts.map +1 -0
  202. package/dts/jobs/browser/unauthorized.d.ts +9 -0
  203. package/dts/jobs/browser/unauthorized.d.ts.map +1 -0
  204. package/dts/jobs/index.d.ts +24 -0
  205. package/dts/jobs/index.d.ts.map +1 -0
  206. package/dts/jobs/jobsUi.d.ts +63 -0
  207. package/dts/jobs/jobsUi.d.ts.map +1 -0
  208. package/dts/jobs/shell.d.ts +77 -0
  209. package/dts/jobs/shell.d.ts.map +1 -0
  210. package/dts/jobs/types.d.ts +146 -0
  211. package/dts/jobs/types.d.ts.map +1 -0
  212. package/dts/kit/browser/shared.d.ts +10 -0
  213. package/dts/kit/browser/shared.d.ts.map +1 -0
  214. package/dts/kit/browser/unauthorized.d.ts +74 -0
  215. package/dts/kit/browser/unauthorized.d.ts.map +1 -0
  216. package/dts/kit/unauthorized.d.ts +47 -0
  217. package/dts/kit/unauthorized.d.ts.map +1 -0
  218. package/guides/docs.md +170 -0
  219. package/guides/jobs.md +1120 -0
  220. package/lib/docs/assets.ts +427 -0
  221. package/lib/docs/browser/config.ts +66 -0
  222. package/lib/docs/browser/index.ts +24 -0
  223. package/lib/docs/docsUi.ts +623 -0
  224. package/lib/docs/hosted.ts +31 -0
  225. package/lib/docs/index.ts +37 -0
  226. package/lib/docs/shell.ts +156 -0
  227. package/lib/docs/types.ts +149 -0
  228. package/lib/index.ts +27 -0
  229. package/lib/jobs/assets.ts +448 -0
  230. package/lib/jobs/browser/config.ts +65 -0
  231. package/lib/jobs/browser/index.ts +9 -0
  232. package/lib/jobs/browser/unauthorized.ts +11 -0
  233. package/lib/jobs/index.ts +41 -0
  234. package/lib/jobs/jobsUi.ts +709 -0
  235. package/lib/jobs/shell.ts +205 -0
  236. package/lib/jobs/types.ts +162 -0
  237. package/lib/kit/browser/shared.ts +11 -0
  238. package/lib/kit/browser/unauthorized.ts +114 -0
  239. package/lib/kit/unauthorized.ts +222 -0
  240. package/package.json +198 -4
@@ -0,0 +1,5 @@
1
+ import{g,P,fe,m,Fe,In}from"./main-a250ffk7.js";var _e={all:["workers"],list:(e={})=>["workers","list",c(e)],ofKey:(e,r)=>["workers","key",e,r],ofQueue:(e)=>["queue",e,"workers"]};function ze(e){return[_e.all,_e.ofQueue(e)]}function c(e){let r={},t=["queue","service","host","state","key"];for(let o of t){let n=e[o];if(n&&n.length>0)r[o]=[...n]}if(e.includeOffline===!0)r.includeOffline=!0;return r}function hn(e,r={},t){return e.request("GET","/workers",{query:c(r),signal:t})}function He(e,r){return`/workers/${P(e)}/${P(r)}`}function To(e){if(e.length===0)return;return e.some((r)=>r.host!==void 0)}function Gn(e,r){let t=new Set;for(let o of e){let n=o[r];if(n!==void 0)t.add(n)}return[...t].sort(u)}function u(e,r){return e<r?-1:e>r?1:0}function f(e){let r=new Map;for(let o of e){let s=o.host===void 0&&o.pid===void 0?"hidden":`${o.host??"?"}:${o.pid??"?"}`,i=r.get(s);if(!i)i={key:s,host:o.host??null,pid:o.pid??null,workers:[]},r.set(s,i);i.workers.push(o)}let t=[...r.values()];for(let o of t)o.workers.sort((n,s)=>u(n.queue,s.queue)||u(n.id,s.id));return t.sort((o,n)=>u(o.host??"",n.host??"")||(o.pid??0)-(n.pid??0))}function vo(e){let r=new Map;for(let t of e){let o=t.service??"",n=r.get(o);if(n)n.push(t);else r.set(o,[t])}return[...r.entries()].map(([t,o])=>({service:t===""?null:t,servers:f(o),count:o.length})).sort((t,o)=>t.service===null?1:o.service===null?-1:u(t.service,o.service))}function So(e,r){let t=r.trim().toLowerCase().split(/\s+/).filter(Boolean),o=`${e.id} ${e.key??""} ${e.service??""} ${e.queue} ${e.host??""} ${e.pid??""}`.toLowerCase();return t.every((n)=>o.includes(n))}function a(e,r,t=""){return`/queues/${P(e)}/workers/${P(r)}${t}`}function l(e,r){return`/queues/${P(e)}/worker-configs/${P(r)}`}var d=2000;function _r(e,r,t,o,n={},s=d){let i=n.timeout!==void 0||n.persist!==void 0;return e.request("POST",a(r,t,`/${o}`),{query:{wait:s},body:i?n:void 0})}function _o(e,r,t,o){return e.request("PUT",l(r,t),{body:o})}function gn(e,r,t){return e.request("DELETE",l(r,t))}function Jt(e){return e.state??(e.paused?"paused":"running")}function Io(e){return e.control?.enabled===!0}function Qn(e,r=Date.now()){return e.stale??e.expiresAt<=r}function Oe(){let e=m(),{sections:r}=fe(),t=Fe();return g(()=>In({meta:e,sections:r,can:t}).some((o)=>o.id==="workers"),[e,r,t])}function Co(){let e=m(),{sections:r}=fe(),t=Fe();return g(()=>In({meta:e,sections:r,can:t}).some((o)=>o.id==="runners"),[e,r,t])}
2
+ export{_e,ze,hn,He,To,Gn,vo,So,_r,_o,gn,Jt,Io,Qn,Oe,Co};
3
+
4
+ //# debugId=4128DFD69EA831A064756E2164756E21
5
+ //# sourceMappingURL=main-kwmqkjfx.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["app/jobs/api/workers.ts", "app/jobs/screens/workers/routed.ts"],
4
+ "sourcesContent": [
5
+ "import type { ApiClient, QueryValue } from \"./client\";\nimport type {\n WorkerConfigBody,\n WorkerConfigResultDto,\n WorkerControlBody,\n WorkerControlResultDto,\n WorkerDesiredState,\n WorkerDto,\n WorkerListDto,\n WorkerListQuery,\n WorkerState,\n} from \"./types\";\nimport { segment } from \"./client\";\n\n/**\n * The Workers screens' reads and query keys. Every worker read sits under\n * {@link workerKeys.all} (`[\"workers\"]`), so one invalidation after a worker\n * action refreshes the Workers page and each queue's Workers panel alike.\n */\nexport const workerKeys = {\n /** Every worker read. */\n all: [\"workers\"] as const,\n /**\n * `GET /workers`, under the filters it was sent with: the unfiltered list\n * is `list()` (an empty query), so a screen reading it for its filter\n * choices shares one cache entry with a screen showing it.\n */\n list: (query: WorkerListQuery = {}) =>\n [\"workers\", \"list\", listQuery(query)] as const,\n /** `GET /workers?queue=<queue>&key=<key>&includeOffline=true`: one stable key's instances, as the worker page reads them. */\n ofKey: (queue: string, key: string) =>\n [\"workers\", \"key\", queue, key] as const,\n /** `GET /queues/:queue/workers`, as the queue panel reads it. */\n ofQueue: (queue: string) => [\"queue\", queue, \"workers\"] as const,\n};\n\n/** What a worker write invalidates: every worker read, on the Workers page and in each queue panel. */\nexport function workerInvalidations(queue: string) {\n return [workerKeys.all, workerKeys.ofQueue(queue)];\n}\n\n/**\n * A {@link WorkerListQuery} as it is sent and keyed: an empty filter left out\n * rather than sent as `key=` (which the API reads as \"match nothing\"), and\n * `includeOffline` only when it is asked for.\n */\nfunction listQuery(query: WorkerListQuery): Record<string, QueryValue> {\n const out: Record<string, QueryValue> = {};\n const names = [\"queue\", \"service\", \"host\", \"state\", \"key\"] as const;\n for (const name of names) {\n const values = query[name];\n if (values && values.length > 0) {\n out[name] = [...values];\n }\n }\n if (query.includeOffline === true) {\n out.includeOffline = true;\n }\n return out;\n}\n\n/**\n * `GET /workers`: every live worker on the queues the caller may read,\n * narrowed by `query` — every filter exact and repeatable, and the filters\n * ANDed. A `host` filter is answered 400 `INVALID_ARGUMENT` when the API hides\n * hosts (`serialize.exposeHosts: false`), so a caller sends one only when the\n * workers it was shown carry a `host`.\n */\nexport function listWorkers(\n api: ApiClient,\n query: WorkerListQuery = {},\n signal?: AbortSignal,\n): Promise<WorkerListDto> {\n return api.request<WorkerListDto>(\"GET\", \"/workers\", {\n query: listQuery(query),\n signal,\n });\n}\n\n/** The app path of one stable worker key's page: its queue, then the key (a key is unique only within its queue). */\nexport function workerPath(queue: string, key: string): string {\n return `/workers/${segment(queue)}/${segment(key)}`;\n}\n\n/** Whether the API exposes worker hosts, read from workers it returned: `undefined` when there is none to tell by. */\nexport function hostsExposed(items: readonly WorkerDto[]): boolean | undefined {\n if (items.length === 0) {\n return undefined;\n }\n return items.some((worker) => worker.host !== undefined);\n}\n\n/** The distinct values of one field across `items`, sorted, absent values left out. */\nexport function distinctValues(\n items: readonly WorkerDto[],\n field: \"queue\" | \"service\" | \"host\",\n): string[] {\n const values = new Set<string>();\n for (const worker of items) {\n const value = worker[field];\n if (value !== undefined) {\n values.add(value);\n }\n }\n return [...values].sort(byText);\n}\n\n/** One server's workers: a host and pid, or `null` for both when the API hides hosts. */\nexport interface WorkerServer {\n /** Stable key: `host:pid`, or `\"hidden\"`. */\n key: string;\n /** Its host, when exposed. */\n host: string | null;\n /** Its pid, when exposed. */\n pid: number | null;\n /** Its workers, by queue then id. */\n workers: WorkerDto[];\n}\n\n/** One service's servers, as the Workers page groups them. */\nexport interface WorkerServiceGroup {\n /** The service a process named itself (`BunJobs`'s `service` option), or `null` when it named none. */\n service: string | null;\n /** Its servers, by host then pid. */\n servers: WorkerServer[];\n /** How many workers it has, across its servers. */\n count: number;\n}\n\n/** Compares two strings, the way the groupings order their names. */\nfunction byText(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/**\n * Groups workers by the server hosting them (host and pid), servers ordered\n * by host then pid, workers by queue then id. With `exposeHosts: false` every\n * worker lands in one group whose host and pid are `null`.\n */\nexport function groupByServer(items: readonly WorkerDto[]): WorkerServer[] {\n const servers = new Map<string, WorkerServer>();\n for (const worker of items) {\n const hidden = worker.host === undefined && worker.pid === undefined;\n const key = hidden\n ? \"hidden\"\n : `${worker.host ?? \"?\"}:${worker.pid ?? \"?\"}`;\n let server = servers.get(key);\n if (!server) {\n server = {\n key,\n host: worker.host ?? null,\n pid: worker.pid ?? null,\n workers: [],\n };\n servers.set(key, server);\n }\n server.workers.push(worker);\n }\n const out = [...servers.values()];\n for (const server of out) {\n server.workers.sort(\n (a, b) => byText(a.queue, b.queue) || byText(a.id, b.id),\n );\n }\n return out.sort(\n (a, b) => byText(a.host ?? \"\", b.host ?? \"\") || (a.pid ?? 0) - (b.pid ?? 0),\n );\n}\n\n/**\n * Groups workers by the service that runs them, then by server: what the\n * Workers page shows. Services are ordered by name, with the workers of\n * processes that named no service (`service` absent) last, in a group whose\n * `service` is `null`.\n */\nexport function groupByService(\n items: readonly WorkerDto[],\n): WorkerServiceGroup[] {\n const services = new Map<string, WorkerDto[]>();\n for (const worker of items) {\n const name = worker.service ?? \"\";\n const group = services.get(name);\n if (group) {\n group.push(worker);\n } else {\n services.set(name, [worker]);\n }\n }\n return [...services.entries()]\n .map(([name, workers]) => ({\n service: name === \"\" ? null : name,\n servers: groupByServer(workers),\n count: workers.length,\n }))\n .sort((a, b) =>\n a.service === null\n ? 1\n : b.service === null\n ? -1\n : byText(a.service, b.service),\n );\n}\n\n/** Whether a worker's id, key, service, queue, host or pid contains every whitespace-separated word of `filter`, ignoring case. */\nexport function matchesWorker(worker: WorkerDto, filter: string): boolean {\n const words = filter.trim().toLowerCase().split(/\\s+/).filter(Boolean);\n const text =\n `${worker.id} ${worker.key ?? \"\"} ${worker.service ?? \"\"} ${worker.queue} ${worker.host ?? \"\"} ${worker.pid ?? \"\"}`.toLowerCase();\n return words.every((word) => text.includes(word));\n}\n\n/** The API path of one worker: its queue, then its per-incarnation id. */\nfunction workerBase(queue: string, id: string, suffix = \"\"): string {\n return `/queues/${segment(queue)}/workers/${segment(id)}${suffix}`;\n}\n\n/** The API path of one worker config override: its queue, then the stable key it applies to. */\nfunction configBase(queue: string, key: string): string {\n return `/queues/${segment(queue)}/worker-configs/${segment(key)}`;\n}\n\n/**\n * How long a control call waits for the worker to acknowledge, in ms, sent as\n * `?wait=`. Long enough that a driver which delivers instructions promptly\n * (memory, Redis) answers `applied: true`, so the UI can say \"Paused\" rather\n * than \"asked to pause\"; short enough not to hold the button on a driver\n * where the worker only notices at its next poll.\n */\nexport const WORKER_CONTROL_WAIT_MS = 2_000;\n\n/**\n * `POST /queues/:queue/workers/:worker/{pause,resume,stop,start}`: records the\n * instruction for one running worker (by its per-incarnation `id`). 202 means\n * stored, 200 acknowledged; both resolve, and `applied` says which happened.\n *\n * `wait` is a **query** parameter; `timeout` and `persist` travel in the body.\n */\nexport function controlWorker(\n api: ApiClient,\n queue: string,\n id: string,\n action: \"pause\" | \"resume\" | \"stop\" | \"start\",\n body: Omit<WorkerControlBody, \"wait\"> = {},\n wait: number = WORKER_CONTROL_WAIT_MS,\n): Promise<WorkerControlResultDto> {\n const hasBody = body.timeout !== undefined || body.persist !== undefined;\n return api.request<WorkerControlResultDto>(\n \"POST\",\n workerBase(queue, id, `/${action}`),\n { query: { wait }, body: hasBody ? body : undefined },\n );\n}\n\n/**\n * `PUT /queues/:queue/worker-configs/:key`: a merge patch of the override,\n * keyed by the **stable key**, so it reaches every replica carrying it and\n * every one started later. A `null` value clears that setting's override.\n */\nexport function configureWorkers(\n api: ApiClient,\n queue: string,\n key: string,\n body: WorkerConfigBody,\n): Promise<WorkerConfigResultDto> {\n return api.request<WorkerConfigResultDto>(\"PUT\", configBase(queue, key), {\n body,\n });\n}\n\n/** `DELETE /queues/:queue/worker-configs/:key`: drops the whole override, back to what the code asked for. */\nexport function resetWorkerConfig(\n api: ApiClient,\n queue: string,\n key: string,\n): Promise<WorkerConfigResultDto> {\n return api.request<WorkerConfigResultDto>(\"DELETE\", configBase(queue, key));\n}\n\n/**\n * A worker's state, for an API that does not send one yet: `state` when it is\n * there, otherwise `paused` decides between `paused` and `running`.\n */\nexport function workerState(worker: WorkerDto): WorkerState {\n return worker.state ?? (worker.paused ? \"paused\" : \"running\");\n}\n\n/** Which lifecycle action a worker in this state can be asked for, and what it would do. */\nexport const WORKER_ACTION_FOR: Readonly<\n Record<WorkerState, readonly WorkerDesiredState[]>\n> = {\n running: [\"paused\", \"stopped\"],\n paused: [\"running\", \"stopped\"],\n stopping: [],\n stopped: [\"running\"],\n restarting: [],\n};\n\n/** Whether the API can control this worker at all: it says so through `control.enabled`; an older worker sends no `control`. */\nexport function isControllable(worker: WorkerDto): boolean {\n return worker.control?.enabled === true;\n}\n\n/** Whether a worker's record has lapsed (it stopped reporting): `stale`, or an `expiresAt` in the past. */\nexport function isStale(worker: WorkerDto, now = Date.now()): boolean {\n return worker.stale ?? worker.expiresAt <= now;\n}\n",
6
+ "import { useMemo } from \"react\";\nimport { useUiConfig } from \"../../context\";\nimport { buildNav } from \"../../layout/nav\";\nimport { useMeta, useUntargetedCanFn } from \"../../meta/hooks\";\n\n/**\n * Whether the worker pages (`/workers/:queue/:key`) are routed for this\n * caller: exactly when the Workers nav entry exists, since the routes are\n * built from the nav. Read from the **untargeted** map, so a queue's own\n * answer (the queue panel sits inside one) cannot offer a link to a route the\n * app never registered.\n */\nexport function useWorkerPagesRouted(): boolean {\n const meta = useMeta();\n const { sections } = useUiConfig();\n const can = useUntargetedCanFn();\n return useMemo(\n () =>\n buildNav({ meta, sections, can }).some((item) => item.id === \"workers\"),\n [meta, sections, can],\n );\n}\n\n/**\n * Whether the runner pages (`/runners/:runner`) are routed for this caller:\n * exactly when the Runners nav entry exists, since the routes are built from\n * the nav. Read the same way, and for the same reason, as\n * {@link useWorkerPagesRouted} — a section inside a queue's scope must not\n * offer a link to a route the app never registered.\n */\nexport function useRunnerPagesRouted(): boolean {\n const meta = useMeta();\n const { sections } = useUiConfig();\n const can = useUntargetedCanFn();\n return useMemo(\n () =>\n buildNav({ meta, sections, can }).some((item) => item.id === \"runners\"),\n [meta, sections, can],\n );\n}\n"
7
+ ],
8
+ "mappings": "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",
9
+ "debugId": "4128DFD69EA831A064756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,5 @@
1
+ import{e,t,f,i,s,pe,u}from"./main-a250ffk7.js";import{On,D}from"./main-hm1ng3yg.js";import{rs,os}from"./main-9prgjhg4.js";function l(a,d=""){return`/providers/${encodeURIComponent(a)}${d}`}var Kt={list:["providers"],schema:(a)=>["providers","schema",a]};async function bn(a,d){let r=await a.request("GET","/providers",{signal:d});return pe(r,(o)=>Array.isArray(o.providers)&&o.providers.every((n)=>typeof n==="object"&&n!==null&&typeof n.id==="string"&&typeof n.readiness==="string"),"the providers","/providers")}async function m(a,d){let p=l(d,"/validate"),r=await a.request("POST",p,{body:{}});return pe(r,(o)=>typeof o.ok==="boolean"&&Array.isArray(o.checks),"a provider validation",p)}async function es(a,d,p){let r=l(d,"/schema"),o=await a.request("GET",r,{signal:p});return pe(o,(n)=>typeof n.schema==="object"&&n.schema!==null,"a provider's config schema",r)}function Nn({providerId:a,label:d}){let p=i(),r=On({mutationFn:()=>m(p,a)}),o=r.data;return t("div",{className:"provider-test","data-testid":`provider-test-${a}`,children:[e(s,{size:"sm",onClick:()=>r.mutate(),disabled:r.isPending,"aria-label":`Test connection: ${d}`,children:r.isPending?"Testing…":"Test connection"}),r.isError&&e(D,{error:r.error,title:"Could not run the test"}),o!==void 0&&!r.isPending&&t("div",{className:"provider-test-result",role:"status","data-testid":"provider-test-result","data-ok":String(o.ok),children:[e("p",{className:o.ok?"provider-test-ok":"provider-test-fail",children:os(o)}),o.checks.length>0&&e("ul",{className:"provider-checks",children:o.checks.map((n)=>{let c=rs(n.status);return t("li",{"data-testid":`provider-check-${n.id}`,children:[e(f,{tone:c.tone,children:c.label})," ",e("code",{children:u(n.id)}),n.detail!==void 0&&t("span",{className:"muted",children:[" ","— ",u(n.detail)]})]},n.id)})})]})]})}
2
+ export{Kt,bn,es,Nn};
3
+
4
+ //# debugId=B39395577836379B64756E2164756E21
5
+ //# sourceMappingURL=main-n9cf6bxm.js.map
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["app/jobs/api/providers.ts", "app/jobs/screens/providers/TestConnection.tsx"],
4
+ "sourcesContent": [
5
+ "import type { ApiClient } from \"./client\";\nimport type {\n ProviderListDto,\n ProviderSchemaDto,\n ProviderValidationDto,\n} from \"./types\";\nimport { assertShape } from \"./shape\";\n\n/**\n * The compute providers configured in the API's process: `GET /providers`\n * (`providers.read`), \"Test connection\" (`POST /providers/:id/validate`,\n * `providers.validate`) and a provider's config schema (`GET\n * /providers/:id/schema`, `providers.read`).\n *\n * Both actions are opt-in, and a disabled one is not routed at all (404\n * `ROUTE_NOT_FOUND`), the way `queues.summon` is. So the UI gates on the\n * permission first and reads nothing without it. A provider id is\n * `name@version~n`, which needs percent-encoding in a path.\n */\n\n/** `/providers/:id…`, relative to the API base. */\nfunction providerPath(id: string, suffix = \"\"): string {\n return `/providers/${encodeURIComponent(id)}${suffix}`;\n}\n\n/** Query keys of the provider reads. */\nexport const providerKeys = {\n /** `GET /providers`. */\n list: [\"providers\"] as const,\n /** `GET /providers/:id/schema`. */\n schema: (id: string) => [\"providers\", \"schema\", id] as const,\n};\n\n/** `GET /providers`. */\nexport async function listProviders(\n api: ApiClient,\n signal?: AbortSignal,\n): Promise<ProviderListDto> {\n const path = \"/providers\";\n const body = await api.request<unknown>(\"GET\", path, { signal });\n return assertShape<ProviderListDto>(\n body,\n (fields) =>\n Array.isArray(fields.providers) &&\n fields.providers.every(\n (item: unknown) =>\n typeof item === \"object\" &&\n item !== null &&\n typeof (item as { id?: unknown }).id === \"string\" &&\n typeof (item as { readiness?: unknown }).readiness === \"string\",\n ),\n \"the providers\",\n path,\n );\n}\n\n/**\n * `POST /providers/:id/validate`: the provider's preflight (\"Test\n * connection\"). A preflight that fails still answers 200, with `ok: false`\n * and the checks or an `error`, so only a refused or broken request throws.\n */\nexport async function validateProvider(\n api: ApiClient,\n id: string,\n): Promise<ProviderValidationDto> {\n const path = providerPath(id, \"/validate\");\n const body = await api.request<unknown>(\"POST\", path, { body: {} });\n return assertShape<ProviderValidationDto>(\n body,\n (fields) => typeof fields.ok === \"boolean\" && Array.isArray(fields.checks),\n \"a provider validation\",\n path,\n );\n}\n\n/** `GET /providers/:id/schema`: the provider's config schema, secret-free. */\nexport async function getProviderSchema(\n api: ApiClient,\n id: string,\n signal?: AbortSignal,\n): Promise<ProviderSchemaDto> {\n const path = providerPath(id, \"/schema\");\n const body = await api.request<unknown>(\"GET\", path, { signal });\n return assertShape<ProviderSchemaDto>(\n body,\n (fields) => typeof fields.schema === \"object\" && fields.schema !== null,\n \"a provider's config schema\",\n path,\n );\n}\n",
6
+ "import { useMutation } from \"@tanstack/react-query\";\nimport { validateProvider } from \"../../api/providers\";\nimport { Badge } from \"../../components/Badge\";\nimport { Button } from \"../../components/Button\";\nimport { ProblemBanner } from \"../../components/ProblemBanner\";\nimport { useApiClient } from \"../../context\";\nimport { displayText } from \"../../format\";\nimport { providerCheck, validationSummary } from \"./providerText\";\n\n/** Props of {@link TestConnection}. */\nexport interface TestConnectionProps {\n /** The provider's id, `name@version~n`. */\n providerId: string;\n /** What the button's accessible name says it tests, e.g. the provider's name. */\n label: string;\n}\n\n/**\n * \"Test connection\": runs the provider's preflight (`POST\n * /providers/:id/validate`) and shows what it found in place, the summary\n * first and then each check. The caller renders it only where the provider\n * has a preflight and the caller may validate (`providers.validate`, never\n * read-only).\n *\n * A failed preflight is an answer, not an error: it says whose problem it\n * is (the config's, the credentials', the platform's) or that it timed out.\n * Only a refused or broken request shows as a problem banner.\n */\nexport function TestConnection({ providerId, label }: TestConnectionProps) {\n const api = useApiClient();\n const test = useMutation({\n mutationFn: () => validateProvider(api, providerId),\n });\n const result = test.data;\n return (\n <div\n className=\"provider-test\"\n data-testid={`provider-test-${providerId}`}\n >\n <Button\n size=\"sm\"\n onClick={() => test.mutate()}\n disabled={test.isPending}\n aria-label={`Test connection: ${label}`}\n >\n {test.isPending ? \"Testing…\" : \"Test connection\"}\n </Button>\n {test.isError && (\n <ProblemBanner\n error={test.error}\n title=\"Could not run the test\"\n />\n )}\n {result !== undefined && !test.isPending && (\n <div\n className=\"provider-test-result\"\n role=\"status\"\n data-testid=\"provider-test-result\"\n data-ok={String(result.ok)}\n >\n <p className={result.ok ? \"provider-test-ok\" : \"provider-test-fail\"}>\n {validationSummary(result)}\n </p>\n {result.checks.length > 0 && (\n <ul className=\"provider-checks\">\n {result.checks.map((check) => {\n const status = providerCheck(check.status);\n return (\n <li\n key={check.id}\n data-testid={`provider-check-${check.id}`}\n >\n <Badge tone={status.tone}>{status.label}</Badge>{\" \"}\n <code>{displayText(check.id)}</code>\n {check.detail !== undefined && (\n <span className=\"muted\">\n {\" \"}\n — {displayText(check.detail)}\n </span>\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n )}\n </div>\n );\n}\n"
7
+ ],
8
+ "mappings": "0HAqBA,SAAS,CAAY,CAAC,EAAY,EAAS,GAAY,CACrD,MAAO,cAAc,mBAAmB,CAAE,IAAI,IAIzC,IAAM,GAAe,CAE1B,KAAM,CAAC,WAAW,EAElB,OAAQ,CAAC,IAAe,CAAC,YAAa,SAAU,CAAE,CACpD,EAGO,eAAe,EAAa,CACjC,EACA,EAC0B,CAE1B,IAAM,EAAO,MAAM,EAAI,QAAiB,MAD3B,aACwC,CAAE,QAAO,CAAC,EAC/D,OAAO,GACL,EACA,CAAC,IACC,MAAM,QAAQ,EAAO,SAAS,GAC9B,EAAO,UAAU,MACf,CAAC,IACC,OAAO,IAAS,UAChB,IAAS,MACT,OAAQ,EAA0B,KAAO,UACzC,OAAQ,EAAiC,YAAc,QAC3D,EACF,gBAbW,YAeb,EAQK,eAAe,CAAgB,CACpC,EACA,EACgC,CAChC,IAAM,EAAO,EAAa,EAAI,WAAW,EACnC,EAAO,MAAM,EAAI,QAAiB,OAAQ,EAAM,CAAE,KAAM,CAAC,CAAE,CAAC,EAClE,OAAO,GACL,EACA,CAAC,IAAW,OAAO,EAAO,KAAO,WAAa,MAAM,QAAQ,EAAO,MAAM,EACzE,wBACA,CACF,EAIK,eAAe,EAAiB,CACrC,EACA,EACA,EAC4B,CAC5B,IAAM,EAAO,EAAa,EAAI,SAAS,EACjC,EAAO,MAAM,EAAI,QAAiB,MAAO,EAAM,CAAE,QAAO,CAAC,EAC/D,OAAO,GACL,EACA,CAAC,IAAW,OAAO,EAAO,SAAW,UAAY,EAAO,SAAW,KACnE,6BACA,CACF,EC5DK,SAAS,EAAc,EAAG,aAAY,SAA8B,CACzE,IAAM,EAAM,EAAa,EACnB,EAAO,GAAY,CACvB,WAAY,IAAM,EAAiB,EAAK,CAAU,CACpD,CAAC,EACK,EAAS,EAAK,KACpB,OACE,EAAC,MAAD,CACE,UAAU,gBACV,cAAa,iBAAiB,IAFhC,SAoDE,CAhDA,EAAC,EAAD,CACE,KAAK,KACL,QAAS,IAAM,EAAK,OAAO,EAC3B,SAAU,EAAK,UACf,aAAY,oBAAoB,IAJlC,SAMG,EAAK,UAAY,WAAa,kBAC/B,EACD,EAAK,SACJ,EAAC,EAAD,CACE,MAAO,EAAK,MACZ,MAAM,yBACR,EAED,IAAW,QAAa,CAAC,EAAK,WAC7B,EAAC,MAAD,CACE,UAAU,uBACV,KAAK,SACL,cAAY,uBACZ,UAAS,OAAO,EAAO,EAAE,EAJ3B,SA+BE,CAzBA,EAAC,IAAD,CAAG,UAAW,EAAO,GAAK,mBAAqB,qBAA/C,SACG,GAAkB,CAAM,EACzB,EACD,EAAO,OAAO,OAAS,GACtB,EAAC,KAAD,CAAI,UAAU,kBAAd,SACG,EAAO,OAAO,IAAI,CAAC,IAAU,CAC5B,IAAM,EAAS,GAAc,EAAM,MAAM,EACzC,OACE,EAAC,KAAD,CAEE,cAAa,kBAAkB,EAAM,KAFvC,SAYE,CARA,EAAC,EAAD,CAAO,KAAM,EAAO,KAApB,SAA2B,EAAO,MAAQ,EAAO,IACjD,EAAC,OAAD,UAAO,EAAY,EAAM,EAAE,EAAI,EAC9B,EAAM,SAAW,QAChB,EAAC,OAAD,CAAM,UAAU,QAAhB,SAGE,CAFC,IADH,KAEK,EAAY,EAAM,MAAM,GAC3B,IATC,EAAM,EAWX,EAEL,EACD,GAEJ,GAEJ",
9
+ "debugId": "B39395577836379B64756E2164756E21",
10
+ "names": []
11
+ }
@@ -0,0 +1,5 @@
1
+ import{N,e,t}from"./main-a250ffk7.js";function F({items:n,layout:r="grid",empty:s="—",className:d}){return e("dl",{className:N("kv",`kv-${r}`,d),children:n.map((a,o)=>{if(!a)return null;let l=a.value===null||a.value===void 0||a.value==="",u=a.key??(typeof a.label==="string"?a.label:`row-${o}`);return t("div",{className:"kv-row",children:[e("dt",{className:"kv-label",children:a.label}),t("dd",{className:N("kv-value",l&&"muted"),children:[l?s:a.value,a.hint&&e("span",{className:"kv-hint",children:a.hint})]})]},u)})})}
2
+ export{F};
3
+
4
+ //# debugId=2CAC3E920EAEDBC164756E2164756E21
5
+ //# sourceMappingURL=main-pegp0ygb.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["app/jobs/components/KeyValue.tsx"],
4
+ "sourcesContent": [
5
+ "import type { ReactNode } from \"react\";\nimport { cx } from \"./classNames\";\n\n/** One row of a {@link KeyValue}. */\nexport interface KeyValueItem {\n /** The term, e.g. \"Attempts\". */\n label: ReactNode;\n /** The value: text or any node. `null`, `undefined` and `\"\"` render the `empty` placeholder. */\n value: ReactNode;\n /** A stable React key; defaults to the label when it is a string, else the index. */\n key?: string;\n /** A short note under the value, e.g. \"of 5 allowed\". */\n hint?: ReactNode;\n}\n\n/** Props of {@link KeyValue}. */\nexport interface KeyValueProps {\n /** The rows, in order. Falsy entries are skipped, so rows can be conditional inline. */\n items: readonly (KeyValueItem | false | null | undefined)[];\n /** `\"grid\"` puts label and value side by side; `\"stacked\"` puts the label above. Defaults to `\"grid\"`. */\n layout?: \"grid\" | \"stacked\";\n /** What a missing value shows. Defaults to `\"—\"`. */\n empty?: ReactNode;\n /** Extra class names on the `<dl>`. */\n className?: string;\n}\n\n/** A summary as a definition list: label/value rows (a job's timings, a queue's settings). */\nexport function KeyValue({\n items,\n layout = \"grid\",\n empty = \"—\",\n className,\n}: KeyValueProps) {\n return (\n <dl className={cx(\"kv\", `kv-${layout}`, className)}>\n {items.map((item, index) => {\n if (!item) {\n return null;\n }\n const missing =\n item.value === null || item.value === undefined || item.value === \"\";\n // The index is only the fallback for a row with neither a key nor a\n // string label.\n const rowKey =\n item.key ??\n (typeof item.label === \"string\" ? item.label : `row-${index}`);\n return (\n <div\n key={rowKey}\n className=\"kv-row\"\n >\n <dt className=\"kv-label\">{item.label}</dt>\n <dd className={cx(\"kv-value\", missing && \"muted\")}>\n {missing ? empty : item.value}\n {item.hint && <span className=\"kv-hint\">{item.hint}</span>}\n </dd>\n </div>\n );\n })}\n </dl>\n );\n}\n"
6
+ ],
7
+ "mappings": "sCA4BO,SAAS,CAAQ,EACtB,QACA,SAAS,OACT,QAAQ,IACR,aACgB,CAChB,OACE,EAAC,KAAD,CAAI,UAAW,EAAG,KAAM,MAAM,IAAU,CAAS,EAAjD,SACG,EAAM,IAAI,CAAC,EAAM,IAAU,CAC1B,GAAI,CAAC,EACH,OAAO,KAET,IAAM,EACJ,EAAK,QAAU,MAAQ,EAAK,QAAU,QAAa,EAAK,QAAU,GAG9D,EACJ,EAAK,MACJ,OAAO,EAAK,QAAU,SAAW,EAAK,MAAQ,OAAO,KACxD,OACE,EAAC,MAAD,CAEE,UAAU,SAFZ,SASE,CALA,EAAC,KAAD,CAAI,UAAU,WAAd,SAA0B,EAAK,MAAQ,EACvC,EAAC,KAAD,CAAI,UAAW,EAAG,WAAY,GAAW,OAAO,EAAhD,SAGE,CAFC,EAAU,EAAQ,EAAK,MACvB,EAAK,MAAQ,EAAC,OAAD,CAAM,UAAU,UAAhB,SAA2B,EAAK,KAAO,GACrD,IAPG,CAQL,EAEL,EACD",
8
+ "debugId": "2CAC3E920EAEDBC164756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ import{N,n,e,t}from"./main-a250ffk7.js";import{ae}from"./main-d6ge7w08.js";function i(r){let s=[r],o=r.cause;while(o&&s.length<=5)s.push(o),o=o.cause;return s}function c({error:r}){return t(n,{children:[t("p",{className:"job-error-line",children:[e("strong",{className:"job-error-name",children:r.name}),": ",e("span",{className:"job-error-message",children:r.message}),r.code!==void 0&&t("code",{className:"job-error-code",children:["code ",String(r.code)]})]}),r.data&&Object.keys(r.data).length>0&&e(ae,{value:r.data,label:`${r.name} data`,expandDepth:1}),r.stack&&e("pre",{className:"job-error-stack",children:r.stack})]})}function dt({error:r,label:s="Error"}){let[o,...a]=i(r);return t("div",{className:"job-error",role:"group","aria-label":s,children:[e(c,{error:o}),a.length>0&&e("ol",{className:"job-error-causes","aria-label":"Causes",children:a.map((l,p)=>t("li",{children:[e("span",{className:"job-error-caused-by muted",children:"Caused by"}),e(c,{error:l})]},p))})]})}function dn({label:r,items:s,className:o}){return e("ol",{className:N("log-lines",o),"aria-label":r,children:s.map((a)=>t("li",{className:N("log-line",a.className),children:[a.number!==void 0&&e("span",{className:"log-number","aria-hidden":"true",children:a.number}),a.prefix,e("code",{className:"log-text",children:a.text}),a.suffix]},a.id))})}
2
+ export{dt,dn};
3
+
4
+ //# debugId=0F7CBA5326FD109064756E2164756E21
5
+ //# sourceMappingURL=main-qr573853.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["app/jobs/screens/job/errorChain.ts", "app/jobs/screens/job/ErrorDetails.tsx", "app/jobs/screens/logs/LogLines.tsx"],
4
+ "sourcesContent": [
5
+ "import type { ErrorDto } from \"../../api/types\";\n\n/** How many causes are followed; the API itself stops at five. */\nconst MAX_CAUSES = 5;\n\n/** The error and its causes, outermost first. */\nexport function causeChain(error: ErrorDto): ErrorDto[] {\n const chain: ErrorDto[] = [error];\n let current = error.cause;\n while (current && chain.length <= MAX_CAUSES) {\n chain.push(current);\n current = current.cause;\n }\n return chain;\n}\n",
6
+ "import type { ErrorDto } from \"../../api/types\";\nimport { JsonView } from \"../../components/JsonView\";\nimport { causeChain } from \"./errorChain\";\n\n/** Props of {@link ErrorDetails}. */\nexport interface ErrorDetailsProps {\n /** The error, as the API serialises it. */\n error: ErrorDto;\n /** Accessible name of the block, e.g. \"Failure\". Defaults to `\"Error\"`. */\n label?: string;\n}\n\n/** One level of an error: `Name: message`, its code, extra data and stack. */\nfunction ErrorLevel({ error }: { error: ErrorDto }) {\n return (\n <>\n <p className=\"job-error-line\">\n <strong className=\"job-error-name\">{error.name}</strong>\n {\": \"}\n <span className=\"job-error-message\">{error.message}</span>\n {error.code !== undefined && (\n <code className=\"job-error-code\">code {String(error.code)}</code>\n )}\n </p>\n {error.data && Object.keys(error.data).length > 0 && (\n <JsonView\n value={error.data}\n label={`${error.name} data`}\n expandDepth={1}\n />\n )}\n {error.stack && <pre className=\"job-error-stack\">{error.stack}</pre>}\n </>\n );\n}\n\n/** A serialised error with its cause chain (\"Caused by …\"). */\nexport function ErrorDetails({ error, label = \"Error\" }: ErrorDetailsProps) {\n const [outer, ...causes] = causeChain(error);\n return (\n <div\n className=\"job-error\"\n role=\"group\"\n aria-label={label}\n >\n <ErrorLevel error={outer} />\n {causes.length > 0 && (\n <ol\n className=\"job-error-causes\"\n aria-label=\"Causes\"\n >\n {causes.map((cause, index) => (\n // The chain is positional: a cause has no identity of its own.\n // eslint-disable-next-line react/no-array-index-key\n <li key={index}>\n <span className=\"job-error-caused-by muted\">Caused by</span>\n <ErrorLevel error={cause} />\n </li>\n ))}\n </ol>\n )}\n </div>\n );\n}\n",
7
+ "import type { ReactNode } from \"react\";\nimport { cx } from \"../../components/classNames\";\nimport \"./logs.css\";\n\n/**\n * The scrolling monospace line list both log views draw with: a job's logs\n * (plain strings, paged) and a run's captured output (structured lines,\n * tailed). Only the presentation is shared — what a line *is*, and how it is\n * fetched, differs too much to share more.\n */\n\n/** One line, as the list renders it. */\nexport interface LogLineView {\n /** Identity of the line: a job log's line number, a run log's `seq`. */\n id: string | number;\n /** The number shown in the gutter; omitted for no gutter. */\n number?: number;\n /** The line's text, rendered verbatim (whitespace preserved). */\n text: string;\n /** Shown between the gutter and the text: a run line's time, stream and level. */\n prefix?: ReactNode;\n /** Shown after the text: a marker that capture cut the line, say. */\n suffix?: ReactNode;\n /** Extra class names on the line. */\n className?: string;\n}\n\n/** Props of {@link LogLines}. */\nexport interface LogLinesProps {\n /** Accessible name of the list. */\n label: string;\n /** The lines, in the order they are shown. */\n items: readonly LogLineView[];\n /** Extra class names on the list. */\n className?: string;\n}\n\n/** An ordered list of log lines in a fixed-height scroll box. */\nexport function LogLines({ label, items, className }: LogLinesProps) {\n return (\n <ol\n className={cx(\"log-lines\", className)}\n aria-label={label}\n >\n {items.map((line) => (\n <li\n key={line.id}\n className={cx(\"log-line\", line.className)}\n >\n {line.number !== undefined && (\n <span\n className=\"log-number\"\n aria-hidden=\"true\"\n >\n {line.number}\n </span>\n )}\n {line.prefix}\n <code className=\"log-text\">{line.text}</code>\n {line.suffix}\n </li>\n ))}\n </ol>\n );\n}\n"
8
+ ],
9
+ "mappings": "2EAMO,SAAS,CAAU,CAAC,EAA6B,CACtD,IAAM,EAAoB,CAAC,CAAK,EAC5B,EAAU,EAAM,MACpB,MAAO,GAAW,EAAM,QANP,EAOf,EAAM,KAAK,CAAO,EAClB,EAAU,EAAQ,MAEpB,OAAO,ECAT,SAAS,CAAU,EAAG,SAA8B,CAClD,OACE,cAiBE,CAhBA,EAAC,IAAD,CAAG,UAAU,iBAAb,SAOE,CANA,EAAC,SAAD,CAAQ,UAAU,iBAAlB,SAAoC,EAAM,KAAO,EAChD,KACD,EAAC,OAAD,CAAM,UAAU,oBAAhB,SAAqC,EAAM,QAAU,EACpD,EAAM,OAAS,QACd,EAAC,OAAD,CAAM,UAAU,iBAAhB,SAA4D,CAA5D,QAAuC,OAAO,EAAM,IAAI,GAAI,GAE9D,EACD,EAAM,MAAQ,OAAO,KAAK,EAAM,IAAI,EAAE,OAAS,GAC9C,EAAC,GAAD,CACE,MAAO,EAAM,KACb,MAAO,GAAG,EAAM,YAChB,YAAa,EACf,EAED,EAAM,OAAS,EAAC,MAAD,CAAK,UAAU,kBAAf,SAAkC,EAAM,MAAQ,GAChE,EAKC,SAAS,EAAY,EAAG,QAAO,QAAQ,SAA8B,CAC1E,IAAO,KAAU,GAAU,EAAW,CAAK,EAC3C,OACE,EAAC,MAAD,CACE,UAAU,YACV,KAAK,QACL,aAAY,EAHd,SAqBE,CAhBA,EAAC,EAAD,CAAY,MAAO,EAAO,EACzB,EAAO,OAAS,GACf,EAAC,KAAD,CACE,UAAU,mBACV,aAAW,SAFb,SAIG,EAAO,IAAI,CAAC,EAAO,IAGlB,EAAC,KAAD,UAGE,CAFA,EAAC,OAAD,CAAM,UAAU,4BAAhB,qBAAuD,EACvD,EAAC,EAAD,CAAY,MAAO,EAAO,IAFnB,CAGP,CACH,EACD,GAEJ,ECvBC,SAAS,EAAQ,EAAG,QAAO,QAAO,aAA4B,CACnE,OACE,EAAC,KAAD,CACE,UAAW,EAAG,YAAa,CAAS,EACpC,aAAY,EAFd,SAIG,EAAM,IAAI,CAAC,IACV,EAAC,KAAD,CAEE,UAAW,EAAG,WAAY,EAAK,SAAS,EAF1C,SAeE,CAXC,EAAK,SAAW,QACf,EAAC,OAAD,CACE,UAAU,aACV,cAAY,OAFd,SAIG,EAAK,OACN,EAEH,EAAK,OACN,EAAC,OAAD,CAAM,UAAU,WAAhB,SAA4B,EAAK,KAAO,EACvC,EAAK,SAbD,EAAK,EAcV,CACH,EACD",
10
+ "debugId": "0F7CBA5326FD109064756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,4 @@
1
+ import{C,c,r,Z,Xe,b,n,e,t,f,i,gt,m,y,ie,J,Y,a,Q,s,Yt,de,E,oe,ue,rt,ye,H,d,p,R,L,o,z,h,u,te,Ce,l,M,V,S,w,Le,Be,I,ne}from"./main-a250ffk7.js";import{yr,kn,se,lt,We,Mt,Er,Xr,eo,to,no,ro,oo,so,xn,io,ao,on,Ft,sn,Ot,Je,an,un,ln}from"./main-szjdmydc.js";import{dt,dn}from"./main-qr573853.js";import{Ue,ct}from"./main-tnbhrm9n.js";import{He,Oe}from"./main-kwmqkjfx.js";import{k}from"./main-4g69fn3x.js";import{O}from"./main-4j1fcevh.js";import{A}from"./main-0jmeh6mq.js";import{ae}from"./main-d6ge7w08.js";import{re}from"./main-jkvd92z9.js";import{D}from"./main-hm1ng3yg.js";import{ge}from"./main-5grjvsy7.js";import{F}from"./main-pegp0ygb.js";function wn(g){return g.open?e(at,{...g}):null}function at({queue:g,onClose:v}){let B=i(),x=m(),P=te(),T=Yt(),N=c(),q=x.limits.maxJobDataBytes,_=x.addableNames,U=_!==null&&_.length===0,[W,G]=r(()=>on({addableNames:_,maxBytes:q})),[K,ee]=r(!1),me=Ce(),le=O({mutationFn:(X)=>ao(B,g,X),invalidate:lt(g),toastErrors:!1,onSuccess:(X)=>{let Ee={label:"View job",onClick:()=>T(We(g,X.job.id))};if(X.added)P.success(`Job added to ${g}`,{description:`Id ${X.job.id}`,action:Ee});else P.info("A job with this id already exists",{description:`Nothing was added; ${X.job.id} is the existing job.`,action:Ee});v()}}),Ne=K?Ft(W):{},ce=le.fieldErrors,pe=b(le.error)?le.error:null,Ae=pe?.code==="NAME_NOT_ADDABLE"||pe?.code==="SERIALIZATION"?pe.code:null,he=(X)=>(X==="opts.runAt"?me.problems["opts.runAt"]:void 0)??Ne[X]??ce[X]??(X==="name"&&Ae==="NAME_NOT_ADDABLE"&&pe?Ot(pe,"add")??pe.detail:void 0)??(X==="data"&&Ae==="SERIALIZATION"&&pe?Ot(pe,"add")??pe.detail:void 0),j=le.error&&!Ae&&(Object.keys(ce).length===0||le.unplacedIssues.length>0)?le.error:null,fe=ce.body??ce.opts,be=(X)=>G((Ee)=>({...Ee,...X})),ve=(X)=>{if(X.preventDefault(),ee(!0),U||me.blocked||Object.keys(Ft(W)).length>0)return;le.mutate(sn(W))},ke=()=>{if(!le.isPending)v()};return e(oe,{open:!0,onClose:ke,title:t(n,{children:["Add a job to ",e("code",{children:g})]}),size:"lg",closeOnEscape:!le.isPending,closeOnBackdrop:!le.isPending,footer:t(n,{children:[e(s,{onClick:ke,disabled:le.isPending,children:"Cancel"}),e(s,{variant:"primary",type:"submit",form:N,disabled:le.isPending||U||me.blocked,"aria-busy":le.isPending||void 0,children:le.isPending?"Adding…":"Add job"})]}),children:U?e(d,{title:"No job can be added here",description:"This API accepts no job names: adding jobs is switched off (the jobs.add action is not enabled, or the API is read-only), or no name is allowed. Ask whoever runs it to list the names in addableNames."}):t("form",{id:N,className:"job-form",onSubmit:ve,noValidate:!0,children:[e(an,{value:W.name,onChange:(X)=>be({name:X}),error:he("name"),addableNames:_}),e(un,{value:W.data.text,onChange:(X)=>be({data:X}),error:he("data"),maxBytes:q}),e(ln,{form:W,patch:be,fieldError:he,onRunAtProblem:(X)=>me.report("opts.runAt",X)}),fe&&e("p",{className:"field-error job-form-error",role:"alert",children:fe}),j&&e(D,{error:j})]})})}var xe=8;function Re(g){let v=Array.from(g);if(v.length<=40)return{text:g,whole:!0};return{text:v.slice(-8).join(""),whole:!1}}function _e(g){return g.open?e(it,{...g}):null}function it({job:g,onClose:v}){let B=i(),[x,P]=r(""),T=O({mutationFn:async(U)=>await Je("fail",()=>io(B,g.queue,g.id,U)),successMessage:"Job failed: it is dead, with no attempts left",invalidate:lt(g.queue),toastErrors:!1}),N=!/\S/.test(x),q=x.length>xn,_=Re(g.id);return t(k,{open:!0,onClose:v,variant:"danger",title:"Fail this job?",description:t(n,{children:["Job ",e("code",{children:g.id})," goes to dead for good, with your reason as its failure, whatever attempts it has left. It gets the"," ",e("code",{children:"failed"})," and ",e("code",{children:"dead"})," events and a copy in its dead-letter queue."]}),confirmLabel:"Fail job",pendingLabel:"Failing…",confirmText:_.text,confirmTextLabel:_.whole?void 0:t(n,{children:["Type the id's last ",xe," characters,"," ",e("code",{children:_.text}),", to confirm"]}),confirmDisabled:N||q,onConfirm:()=>T.mutateAsync(x),children:[g.state==="active"&&e("p",{className:"job-note job-note-warning","data-testid":"fail-active-note",children:"This job is running. Failing it does not stop its code: its worker loses the job's lock at its next heartbeat and the attempt's signal is aborted, but the processor runs until it returns, and whatever it returns or throws is discarded."}),e(l,{label:"Reason",required:!0,hint:`Recorded as the job's failure, exactly as typed. At most ${o(xn)} characters.`,error:T.fieldErrors.reason??(q?`At most ${o(xn)} characters.`:x!==""&&N?"Spaces alone are not a reason: type some text.":void 0),children:e(M,{value:x,onChange:P,disabled:T.isPending})})]})}function st(g){return g!=="completed"&&g!=="dead"}function Te(g){let{readOnly:v}=m(),B=y("jobs.retry"),x=y("jobs.promote"),P=y("jobs.remove"),T=y("jobs.update"),N=y("jobs.fail"),q=ie("update");return{retry:!v&&B&&kn(g.state),promote:!v&&x&&g.state==="delayed",remove:!v&&P,update:!v&&T&&q,fail:!v&&N&&st(g.state)}}function Fe(g){let v={};if(!g.changeData&&g.priority===void 0&&g.runAt===void 0)v.form="Change at least one of data, priority or run time.";if(g.changeData){if(g.data.error)v.data="The data is not valid JSON.";else if(g.data.overLimit)v.data="The data is larger than the API accepts.";else if(g.data.value===void 0)v.data="Enter the new data as JSON (null is allowed)."}if(g.priority!==void 0&&!Number.isFinite(g.priority))v.priority="Enter a number.";if(g.conditional&&g.onlyIn.length===0)v.onlyIn="Pick at least one state, or untick the condition.";return v}function je(g){return{...g.changeData?{data:g.data.value}:{},...g.priority===void 0?{}:{priority:g.priority},...g.runAt===void 0?{}:{runAt:g.runAt},...g.conditional?{onlyIn:g.onlyIn}:{}}}function qe(g){return g.open?e(mt,{...g}):null}function mt({job:g,onClose:v}){let B=i(),{limits:x}=m(),P=c(),T=x.maxJobDataBytes,[N,q]=r(()=>({changeData:!1,data:Ue(JSON.stringify(g.data??null,null,2),{maxBytes:T}),priority:void 0,runAt:void 0,conditional:!1,onlyIn:[g.state]})),[_,U]=r(!1),W=Ce(),G=O({mutationFn:async(j)=>await Je("update",()=>no(B,g.queue,g.id,j)),successMessage:"Job updated",invalidate:lt(g.queue),toastErrors:!1,onSuccess:()=>v()}),K=_?Fe(N):{},ee=G.fieldErrors,me=(j)=>(j==="runAt"?W.problems.runAt:void 0)??K[j]??ee[j],le=G.error&&!(b(G.error)&&Object.keys(ee).length>0&&G.unplacedIssues.length===0)?G.error:null,Ne=ee.body,ce=(j)=>q((fe)=>({...fe,...j})),pe=(j)=>{if(j.preventDefault(),U(!0),W.blocked||Object.keys(Fe(N)).length>0)return;G.mutate(je(N))},Ae=(j,fe)=>q((be)=>({...be,onlyIn:fe?Z.filter((ve)=>ve===j||be.onlyIn.includes(ve)):be.onlyIn.filter((ve)=>ve!==j)})),he=()=>{if(!G.isPending)v()};return e(oe,{open:!0,onClose:he,title:"Edit job",description:"Change what you tick or fill in; everything else stays as it is.",size:"lg",closeOnEscape:!G.isPending,closeOnBackdrop:!G.isPending,footer:t(n,{children:[e(s,{onClick:he,disabled:G.isPending,children:"Cancel"}),e(s,{variant:"primary",type:"submit",form:P,disabled:G.isPending||W.blocked,"aria-busy":G.isPending||void 0,children:G.isPending?"Saving…":"Save"})]}),children:t("form",{id:P,className:"job-form",onSubmit:pe,noValidate:!0,children:[e(w,{checked:N.changeData,onChange:(j)=>ce({changeData:j}),label:"Replace the data"}),N.changeData&&e(l,{label:"Data",hint:"JSON; null is allowed.",error:me("data"),children:e(ct,{value:N.data.text,onChange:(j)=>ce({data:j}),maxBytes:T,rows:8})}),e(l,{label:"Priority",hint:`Lower runs first. Now ${g.priority}; leave empty to keep it.`,error:me("priority"),children:e(V,{value:N.priority,onChange:(j)=>ce({priority:j}),step:"any",placeholder:String(g.priority)})}),e(l,{label:"Run at",hint:"Moves only a waiting or delayed job. Leave empty to keep it.",error:me("runAt"),children:e(Le,{value:N.runAt,onChange:(j)=>ce({runAt:j}),onProblem:(j)=>W.report("runAt",j)})}),e(w,{checked:N.conditional,onChange:(j)=>ce({conditional:j}),label:"Only if the job is still in one of these states",hint:"The change is skipped (and reported) if the job has moved on."}),N.conditional&&t("fieldset",{className:"job-states-picker","aria-invalid":Boolean(me("onlyIn"))||void 0,children:[e("legend",{children:"Only in"}),Z.map((j)=>e(w,{checked:N.onlyIn.includes(j),onChange:(fe)=>Ae(j,fe),label:z[j]},j)),me("onlyIn")&&e("p",{className:"field-error",children:me("onlyIn")})]}),(K.form??Ne)&&e("p",{className:"field-error job-form-error",role:"alert",children:K.form??Ne}),le&&e(D,{error:le})]})})}function Me({job:g}){let v=i(),B=J(),x=Yt(),P=Te(g),[T,N]=r(null),[q,_]=r(!0),U=lt(g.queue),W=O({mutationFn:async(ee)=>await Je("retry",()=>oo(v,g.queue,g.id,ee)),successMessage:"Job retried: it is waiting to run again",invalidate:U,toastErrors:!1}),G=O({mutationFn:()=>Je("promote",()=>so(v,g.queue,g.id)),successMessage:"Job promoted: it can run now",errorTitle:"Could not promote the job",invalidate:U}),K=O({mutationFn:()=>Je("remove",()=>ro(v,g.queue,g.id)),successMessage:"Job removed",invalidate:U,toastErrors:!1,onSuccess:async()=>{await B.cancelQueries({queryKey:se.job(g.queue,g.id)}),B.removeQueries({queryKey:se.job(g.queue,g.id)}),x(Mt(g.queue))}});if(!P.retry&&!P.promote&&!P.remove&&!P.update&&!P.fail)return null;return t("div",{className:"job-actions",role:"group","aria-label":"Job actions",children:[P.retry&&e(s,{onClick:()=>{_(!0),N("retry")},children:"Retry"}),P.promote&&e(s,{disabled:G.isPending,"aria-busy":G.isPending||void 0,onClick:()=>G.mutate(),children:G.isPending?"Promoting…":"Promote"}),P.update&&e(s,{onClick:()=>N("update"),children:"Edit"}),P.fail&&e(s,{variant:"danger",onClick:()=>N("fail"),children:"Fail…"}),P.remove&&e(s,{variant:"danger",onClick:()=>N("remove"),children:"Remove"}),e(k,{open:T==="retry",onClose:()=>N(null),title:"Retry this job?",description:"It goes back to waiting and runs again.",confirmLabel:"Retry",pendingLabel:"Retrying…",onConfirm:()=>W.mutateAsync(q),children:e(w,{checked:q,onChange:_,label:"Reset attempts",hint:"Count attempts from zero again, so it gets its full allowance of retries."})}),e(k,{open:T==="remove",onClose:()=>N(null),variant:"danger",title:"Remove this job?",description:t(n,{children:["Job ",e("code",{children:g.id})," and its logs are deleted. This cannot be undone."]}),confirmLabel:"Remove",pendingLabel:"Removing…",onConfirm:()=>K.mutateAsync()}),e(_e,{job:g,open:T==="fail",onClose:()=>N(null)}),e(qe,{job:g,open:T==="update",onClose:()=>N(null)})]})}var Ge=Q,Se=3000,ut=new Set(["active","waiting"]);function Pe(g){return g===void 0||kn(g)?!1:Ge}function De(g){return g!==void 0&&ut.has(g)?Se:!1}function $e(g){return De(g)!==!1}var Ve=[50,100,200,500];function Qe(g){return Math.max(1,Math.min(100,g))}function Ke(){let g=H(Ge);return C((v)=>Pe(v)===!1?!1:g,[g])}var pt=new Set(["progress","duplicate","throttled"]),ft=Xe.filter((g)=>!pt.has(g));function bt(g){return typeof g==="number"&&Number.isFinite(g)||typeof g==="object"&&g!==null&&!Array.isArray(g)}function ze(g,v,B){let x=J(),P=[ue.job(g,v)];ye(P,[se.job(g,v)],{events:ft,enabled:B}),rt({channels:P,events:["progress"],enabled:B,onEvent:(T)=>{if(T.kind!=="queue"||T.type!=="progress")return;let{progress:N}=T.payload;if(!bt(N)){x.invalidateQueries({queryKey:se.job(g,v),exact:!0});return}let q=(_)=>_?{..._,progress:N}:_;x.setQueryData(se.job(g,v),q)}})}function Ye({queue:g,id:v}){return t(E,{to:We(g,v),className:"job-ref",children:[e("span",{className:"muted",children:g})," / ",e("code",{children:v})]})}function yt({child:g}){if(g.failure)return e(dt,{error:g.failure,label:`Failure of ${g.id}`});if(g.value!==void 0)return e(ae,{value:g.value,label:`Value of ${g.id}`,expandDepth:1});return e("span",{className:"muted",children:"—"})}function ht({job:g}){let v=i(),B=a({queryKey:se.children(g.queue,g.id),queryFn:({signal:P})=>to(v,g.queue,g.id,P),refetchInterval:Pe(g.state)});if(B.isPending)return e(p,{label:"Loading the children",showLabel:!0});if(B.isError)return e(D,{error:B.error,title:"Could not load the children",onRetry:()=>void B.refetch()});let x=B.data;return t(n,{children:[t(I,{label:"Children",children:[e("thead",{children:t("tr",{children:[e("th",{scope:"col",children:"Child"}),e("th",{scope:"col",children:"State"}),e("th",{scope:"col",children:"Outcome"})]})}),e("tbody",{children:x.children.map((P)=>t("tr",{"data-testid":`flow-child-${P.queue}:${P.id}`,children:[e("th",{scope:"row",children:e(Ye,{queue:P.queue,id:P.id})}),e("td",{children:P.job?e(Be,{state:P.job.state}):e(f,{tone:"warning",title:"The child is gone, or in a queue this API cannot reach",children:"unreachable"})}),e("td",{children:e(yt,{child:P})})]},`${P.queue}:${P.id}`))})]}),x.truncated&&t("p",{className:"notice",role:"note","data-testid":"flow-truncated",children:["Showing the first ",h(x.children.length,"child","children"),": this job has more than the API lists at once."]})]})}function Ze({job:g}){let v=g.flow;if(!v)return null;return t(R,{title:"Flow",children:[t("dl",{className:"kv kv-grid job-flow-summary",children:[t("div",{className:"kv-row",children:[e("dt",{className:"kv-label",children:"Parent"}),e("dd",{className:"kv-value",children:v.parent?e(Ye,{queue:v.parent.queue,id:v.parent.id}):e("span",{className:"muted",children:"None: this is the root"})})]}),t("div",{className:"kv-row",children:[e("dt",{className:"kv-label",children:"Pending children"}),e("dd",{className:"kv-value",children:o(v.pending)})]})]}),v.children.length>0?e(ht,{job:g}):e("p",{className:"muted",children:"This job has no children."})]})}function et(g,v){if(g==="active")return"Clear logs once the job finishes — a running job is still writing to them.";if(v===0)return"No lines to clear.";return null}function tt({queue:g,id:v,state:B,total:x,onCleared:P}){let T=i(),N=J(),q=re(),_=c(),[U,W]=r(!1),G=O({mutationFn:()=>Je("clearLogs",()=>eo(T,g,v)),successMessage:(ee)=>`Cleared ${h(ee.removed,"line")}`,invalidate:[se.logsAll(g,v)],toastErrors:!1,onSuccess:()=>P?.(),onError:(ee)=>{if(b(ee)&&(ee.code==="JOB_ACTIVE"||ee.code==="JOB_NOT_FOUND"))N.invalidateQueries({queryKey:se.job(g,v)})}});if(!q("jobs.clearLogs"))return null;let K=et(B,x);return t("span",{className:"job-logs-clear",children:[e(s,{variant:"danger",size:"sm",disabled:K!==null||x===null,"aria-describedby":K?_:void 0,onClick:()=>W(!0),children:"Clear logs…"}),K&&e("span",{id:_,className:"muted job-logs-clear-reason","data-testid":"clear-logs-reason",children:K}),e(k,{open:U,onClose:()=>W(!1),variant:"danger",title:`Clear the ${h(x??0,"log line")} of this job?`,description:t(n,{children:["Job ",e("code",{children:v}),"'s log is emptied, and its next line is numbered 1 again. The job itself, its state and its counters are untouched. This cannot be undone."]}),confirmLabel:"Clear logs",pendingLabel:"Clearing…",onConfirm:()=>G.mutateAsync()})]})}var vt=[{value:"asc",label:"Oldest first"},{value:"desc",label:"Newest first"}];function ot({queue:g,id:v,state:B}){let x=i(),{limits:P}=m(),T=c(),[N,q]=r(()=>({offset:0,limit:Qe(P.maxLogPage),order:"asc"})),_=a({queryKey:se.logs(g,v,N),queryFn:({signal:K})=>Xr(x,g,v,N,K),placeholderData:Y,refetchInterval:De(B)}),U=_.data,W=U?.page.total??0,G=(K)=>N.order==="asc"?N.offset+K+1:W-N.offset-K;return t(R,{title:"Logs",actions:t("span",{className:"job-logs-controls",children:[t("span",{className:"job-logs-order",children:[e("label",{htmlFor:T,children:"Order"}),e(S,{id:T,options:vt,value:N.order,onChange:(K)=>q((ee)=>({...ee,order:K,offset:0}))})]}),e(tt,{queue:g,id:v,state:B,total:U?W:null,onCleared:()=>q((K)=>({...K,offset:0}))})]}),children:[$e(B)&&t("p",{className:"muted job-logs-follow","data-testid":"logs-follow",children:["Refreshing every ",Se/1000," s while the job is ",B,"."]}),!U?_.isError?e(D,{error:_.error,title:"Could not load the logs",onRetry:()=>void _.refetch()}):e(p,{label:"Loading the logs",showLabel:!0}):t(n,{children:[_.isError&&e(D,{error:_.error,title:"Could not refresh the logs",onRetry:()=>void _.refetch()}),U.items.length===0?e("p",{className:"muted",children:W===0?"No log lines yet.":"No lines on this page."}):e(dn,{label:"Log lines",items:U.items.map((K,ee)=>({id:G(ee),number:G(ee),text:K}))}),e(ne,{label:"Log pages",offset:N.offset,limit:N.limit,total:W,itemCount:U.items.length,hasMore:U.page.hasMore,pageSizes:Ve,maxPageSize:P.maxLogPage,onChange:(K)=>q((ee)=>({...ee,...K}))}),t("p",{className:"muted job-logs-total",children:[o(W)," ",W===1?"line":"lines"," in all"]})]})]})}function we(g){return b(g)&&(g.code==="JOB_NOT_FOUND"||g.code==="QUEUE_NOT_FOUND")}function Ie(g){return b(g)&&g.isAuth}var Jt=new Intl.NumberFormat(void 0,{maximumFractionDigits:2});function At(g){if(Number.isNaN(g))return 0;return Math.min(100,Math.max(0,g))}function Nt({progress:g}){if(g===null||g===void 0)return null;if(typeof g==="number"){let v=At(g),B=`${Jt.format(g)}%`;return t("span",{className:"job-progress","data-testid":"job-progress",children:[e("span",{className:"job-progress-bar",role:"progressbar","aria-label":"Progress","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":v,"aria-valuetext":B,children:e("span",{className:"job-progress-fill",style:{width:`${v}%`}})}),e("span",{className:"job-progress-value",children:B})]})}if(typeof g==="object")return e(ae,{value:g,label:"Progress",expandDepth:1,copyable:!1});return e("span",{children:String(g)})}function Ct({queue:g,worker:v}){let B=Oe(),x=v.host!==void 0||v.pid!==void 0?[v.host,v.pid===void 0?void 0:`pid ${v.pid}`].filter((P)=>P!==void 0).join(", "):null;return t("span",{className:"job-processed-by","data-testid":"job-processed-by",children:[v.key!==void 0&&(B?e(E,{to:He(g,v.key),"data-testid":"job-processed-by-key",children:v.key}):e("span",{"data-testid":"job-processed-by-key",children:v.key})),t("span",{className:"muted",children:[v.key!==void 0?"incarnation ":"worker ",e("code",{children:v.id})]}),x!==null&&e("span",{className:"muted",children:x})]})}function Et({job:g}){let v=ie("jobAttribution");return e(F,{className:"job-summary",items:[{label:"State",value:e(Be,{state:g.state})},{label:"Priority",value:o(g.priority)},{label:"Attempts",value:`${o(g.attemptsMade)} of ${o(g.maxAttempts)}`},{label:"Stalled",value:o(g.stalledCount)},{label:"Run at",value:e(A,{value:g.runAt})},{label:"Created",value:e(A,{value:g.createdAt})},{label:"Processed",value:e(A,{value:g.processedOn})},{label:"Finished",value:e(A,{value:g.finishedOn})},{label:"Expires",value:e(A,{value:g.expiresAt})},{label:"Lock expires",value:e(A,{value:g.lockExpiresAt})},g.state==="active"&&{label:"Held by",value:g.workerId?e("code",{children:g.workerId}):null},v&&{label:"Processed by",key:"processedBy",value:g.processedBy?e(Ct,{queue:g.queue,worker:g.processedBy}):e("span",{"data-testid":"job-processed-by-none",children:"No worker recorded"}),hint:g.processedBy?"The worker that ran the last attempt; earlier attempts are not recorded.":"The job was never claimed, or was claimed before this backend recorded who ran a job."},{label:"Repeat key",value:g.repeatKey?e("code",{children:g.repeatKey}):null},{label:"Progress",value:e(Nt,{progress:g.progress}),key:"progress"}]})}function Lt({queue:g,id:v}){let B=i(),[x,P]=r(!1),T=a({queryKey:se.stacktrace(g,v),queryFn:({signal:_})=>Er(B,g,v,["stacktrace"],_),enabled:x}),N=(_)=>{if(_.currentTarget.open)P(!0)},q=T.data?.stacktrace??[];return t("details",{className:"job-details",onToggle:N,"data-testid":"job-stacktraces",children:[e("summary",{children:"Failure history (newest first)"}),!x?null:T.isPending?e(p,{label:"Loading the failure history",showLabel:!0}):T.isError?e(D,{error:T.error,title:"Could not load the failure history",onRetry:()=>void T.refetch()}):q.length===0?e("p",{className:"muted",children:"No failures were kept for this job."}):e("ol",{className:"job-stacktraces",children:q.map((_,U)=>e("li",{children:e(dt,{error:_,label:U===0?"Newest failure":`Failure ${U+1}`})},U))})]})}function St({queue:g,id:v}){return e("div",{className:"screen","data-testid":"job-not-found",children:e(d,{headingLevel:1,title:"Job not found",description:t(n,{children:["There is no job ",e("code",{children:v})," in queue ",e("code",{children:g}),". It may have finished and been cleaned up, or been removed."]}),action:t(E,{to:Mt(g),children:["Back to ",g]})})})}function nt({queue:g,detail:v}){return e("div",{className:"screen","data-testid":"job-hidden",children:e(d,{headingLevel:1,title:"Job hidden",description:v??t(n,{children:["You may not read the jobs of queue ",e("code",{children:g}),"."]}),action:t(E,{to:Mt(g),children:["Back to ",g]})})})}function Pt({job:g}){let v=y("jobs.logs"),B=ie("logs"),x=g.returnValue!==void 0&&(g.state==="completed"||g.returnValue!==null);return t(n,{children:[t("header",{className:"job-header",children:[t("div",{className:"job-heading",children:[t("h1",{className:"screen-title job-title",children:[e("span",{className:"job-name",children:u(g.name)}),e(Be,{state:g.state})]}),t("p",{className:"job-id-line",children:[e("span",{className:"muted",children:"Id"}),e("code",{className:"job-id","data-testid":"job-id",children:g.id}),e(ge,{text:g.id,ariaLabel:"Copy job id",size:"sm"})]})]}),e(Me,{job:g})]}),e(R,{title:"Summary",children:e(Et,{job:g})}),g.failedReason&&t(R,{title:"Failure",children:[e(dt,{error:g.failedReason,label:"Failure"}),e(Lt,{queue:g.queue,id:g.id})]}),e(R,{title:"Data",children:g.data===void 0?e("p",{className:"muted",children:"Not included in this response."}):e(ae,{value:g.data,label:"Job data"})}),x&&e(R,{title:"Return value",children:e(ae,{value:g.returnValue,label:"Return value"})}),g.flow&&e(Ze,{job:g}),B&&v&&e(ot,{queue:g.queue,id:g.id,state:g.state}),g.opts&&e(R,{title:"Options",children:e(ae,{value:g.opts,label:"Job options",expandDepth:1})})]})}function Vs(){let{queue:g="",id:v=""}=de(),B=i(),x=y("jobs.read"),P=gt(),T=Ke(),N=a({queryKey:se.job(g,v),queryFn:({signal:q})=>Er(B,g,v,yr,q),enabled:x&&P,refetchInterval:(q)=>we(q.state.error)||Ie(q.state.error)?!1:T(q.state.data?.state)});if(ze(g,v,x&&P&&!Ie(N.error)&&!we(N.error)),P&&!x)return e(nt,{queue:g});if(Ie(N.error))return e(nt,{queue:g,detail:N.error.detail});if(we(N.error))return e(St,{queue:g,id:v});return t("div",{className:"screen job-screen","data-testid":"job-screen",children:[e("nav",{className:"breadcrumb","aria-label":"Breadcrumb",children:t("ol",{children:[e("li",{children:e(E,{to:"/queues",children:"Queues"})}),e("li",{children:e(E,{to:Mt(g),children:g})}),e("li",{"aria-current":"page",children:"Job"})]})}),N.data?t(n,{children:[N.isError&&e(D,{error:N.error,title:"Could not refresh this job",onRetry:()=>void N.refetch()}),e(Pt,{job:N.data})]}):N.isError?e(L,{error:N.error,headingLevel:1,title:"Could not load the job",onRetry:()=>void N.refetch()}):e(p,{label:"Loading the job",showLabel:!0})]})}export{wn,Vs};
2
+
3
+ //# debugId=C598CA256C5DFC7B64756E2164756E21
4
+ //# sourceMappingURL=main-rq97rdr4.js.map
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["app/jobs/screens/job/AddJobDialog.tsx", "app/jobs/screens/job/failConfirm.ts", "app/jobs/screens/job/FailJobDialog.tsx", "app/jobs/screens/job/jobGates.ts", "app/jobs/screens/job/updateJobForm.ts", "app/jobs/screens/job/UpdateJobDialog.tsx", "app/jobs/screens/job/JobActions.tsx", "app/jobs/screens/job/polling.ts", "app/jobs/screens/job/JobFlow.tsx", "app/jobs/screens/job/clearLogs.ts", "app/jobs/screens/job/ClearLogsButton.tsx", "app/jobs/screens/job/JobLogs.tsx", "app/jobs/screens/job/JobScreen.tsx"],
4
+ "sourcesContent": [
5
+ "import type { FormEvent } from \"react\";\nimport type { AddJobBody, AddJobResultDto } from \"../../api/types\";\nimport type { AddJobField, AddJobForm } from \"./addJobForm\";\nimport { useId, useState } from \"react\";\nimport { isApiError } from \"../../api/errors\";\nimport { addJob, jobScreenPath, mutationInvalidation } from \"../../api/jobs\";\nimport { Button } from \"../../components/Button\";\nimport { Dialog } from \"../../components/Dialog\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { ProblemBanner } from \"../../components/ProblemBanner\";\nimport { useToast } from \"../../components/toast\";\nimport { useApiClient } from \"../../context\";\nimport { useApiMutation } from \"../../hooks/useApiMutation\";\nimport { useFieldProblems } from \"../../hooks/useFieldProblems\";\nimport { useMeta } from \"../../meta/hooks\";\nimport { useNavigate } from \"../../routing\";\nimport { addBody, emptyAddForm, validateAddForm } from \"./addJobForm\";\nimport { explainJobError } from \"./jobErrors\";\nimport { JobDataField, JobNameField, JobOptionsFields } from \"./JobFormFields\";\nimport \"./job.css\";\n\n/** Props of {@link AddJobDialog}. This interface is the contract between the two screens: keep it. */\nexport interface AddJobDialogProps {\n /** The queue the job is added to. */\n queue: string;\n /** Whether the dialog is open. */\n open: boolean;\n /** Called to close it: cancel, Escape, or after a successful add. */\n onClose: () => void;\n}\n\n/** Adds a job to a queue (`POST /queues/:queue/jobs`). */\nexport function AddJobDialog(props: AddJobDialogProps) {\n return props.open ? <OpenAddJobDialog {...props} /> : null;\n}\n\n/** The mounted dialog; its state lives exactly as long as one opening. */\nfunction OpenAddJobDialog({ queue, onClose }: AddJobDialogProps) {\n const api = useApiClient();\n const meta = useMeta();\n const toast = useToast();\n const navigate = useNavigate();\n const formId = useId();\n const maxBytes = meta.limits.maxJobDataBytes;\n const addableNames = meta.addableNames;\n const nothingAddable = addableNames !== null && addableNames.length === 0;\n const [form, setForm] = useState<AddJobForm>(() =>\n emptyAddForm({ addableNames, maxBytes }),\n );\n const [submitted, setSubmitted] = useState(false);\n const times = useFieldProblems<\"opts.runAt\">();\n\n const add = useApiMutation({\n mutationFn: (body: AddJobBody) => addJob(api, queue, body),\n invalidate: mutationInvalidation(queue),\n toastErrors: false,\n onSuccess: (result: AddJobResultDto) => {\n const view = {\n label: \"View job\",\n onClick: () => navigate(jobScreenPath(queue, result.job.id)),\n };\n if (result.added) {\n toast.success(`Job added to ${queue}`, {\n description: `Id ${result.job.id}`,\n action: view,\n });\n } else {\n toast.info(\"A job with this id already exists\", {\n description: `Nothing was added; ${result.job.id} is the existing job.`,\n action: view,\n });\n }\n onClose();\n },\n });\n\n const clientErrors = submitted ? validateAddForm(form) : {};\n const serverErrors = add.fieldErrors;\n const apiError = isApiError(add.error) ? add.error : null;\n // NAME_NOT_ADDABLE belongs to the name, SERIALIZATION to the data.\n const inlineCode =\n apiError?.code === \"NAME_NOT_ADDABLE\" || apiError?.code === \"SERIALIZATION\"\n ? apiError.code\n : null;\n const fieldError = (key: AddJobField): string | undefined =>\n (key === \"opts.runAt\" ? times.problems[\"opts.runAt\"] : undefined) ??\n clientErrors[key] ??\n serverErrors[key] ??\n (key === \"name\" && inlineCode === \"NAME_NOT_ADDABLE\" && apiError\n ? (explainJobError(apiError, \"add\") ?? apiError.detail)\n : undefined) ??\n (key === \"data\" && inlineCode === \"SERIALIZATION\" && apiError\n ? (explainJobError(apiError, \"add\") ?? apiError.detail)\n : undefined);\n const bannerError =\n add.error &&\n !inlineCode &&\n (Object.keys(serverErrors).length === 0 || add.unplacedIssues.length > 0)\n ? add.error\n : null;\n const bodyError = serverErrors.body ?? serverErrors.opts;\n\n const patch = (next: Partial<AddJobForm>) =>\n setForm((current) => ({ ...current, ...next }));\n\n const onSubmit = (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setSubmitted(true);\n if (\n nothingAddable ||\n times.blocked ||\n Object.keys(validateAddForm(form)).length > 0\n ) {\n return;\n }\n add.mutate(addBody(form));\n };\n\n const close = () => {\n if (!add.isPending) {\n onClose();\n }\n };\n\n return (\n <Dialog\n open\n onClose={close}\n title={\n <>\n Add a job to <code>{queue}</code>\n </>\n }\n size=\"lg\"\n closeOnEscape={!add.isPending}\n closeOnBackdrop={!add.isPending}\n footer={\n <>\n <Button\n onClick={close}\n disabled={add.isPending}\n >\n Cancel\n </Button>\n <Button\n variant=\"primary\"\n type=\"submit\"\n form={formId}\n disabled={add.isPending || nothingAddable || times.blocked}\n aria-busy={add.isPending || undefined}\n >\n {add.isPending ? \"Adding…\" : \"Add job\"}\n </Button>\n </>\n }\n >\n {nothingAddable ? (\n <EmptyState\n title=\"No job can be added here\"\n description=\"This API accepts no job names: adding jobs is switched off (the jobs.add action is not enabled, or the API is read-only), or no name is allowed. Ask whoever runs it to list the names in addableNames.\"\n />\n ) : (\n <form\n id={formId}\n className=\"job-form\"\n onSubmit={onSubmit}\n noValidate\n >\n <JobNameField\n value={form.name}\n onChange={(name) => patch({ name })}\n error={fieldError(\"name\")}\n addableNames={addableNames}\n />\n <JobDataField\n value={form.data.text}\n onChange={(data) => patch({ data })}\n error={fieldError(\"data\")}\n maxBytes={maxBytes}\n />\n <JobOptionsFields\n form={form}\n patch={patch}\n fieldError={fieldError}\n onRunAtProblem={(problem) => times.report(\"opts.runAt\", problem)}\n />\n {bodyError && (\n <p\n className=\"field-error job-form-error\"\n role=\"alert\"\n >\n {bodyError}\n </p>\n )}\n {bannerError && <ProblemBanner error={bannerError} />}\n </form>\n )}\n </Dialog>\n );\n}\n",
6
+ "/** Longest job id typed in full to confirm; a longer one is confirmed by its tail. */\nexport const FAIL_CONFIRM_FULL_ID_MAX = 40;\n\n/** Characters of a long id's tail typed to confirm. */\nexport const FAIL_CONFIRM_TAIL_LENGTH = 8;\n\n/** What the fail dialog asks to be typed, and how it says so. */\nexport interface FailConfirmation {\n /** The exact text to type. */\n text: string;\n /** Whether `text` is the whole id (`false`: its last characters). */\n whole: boolean;\n}\n\n/**\n * The typed confirmation for failing job `id`: the id itself, as the other\n * danger dialogs ask for a queue's or runner's name, unless it is longer than\n * {@link FAIL_CONFIRM_FULL_ID_MAX} characters (ids run to 1024). Then it is\n * the id's last {@link FAIL_CONFIRM_TAIL_LENGTH} characters, counted in code\n * points so a surrogate pair is never split.\n */\nexport function failConfirmation(id: string): FailConfirmation {\n const chars = Array.from(id);\n if (chars.length <= FAIL_CONFIRM_FULL_ID_MAX) {\n return { text: id, whole: true };\n }\n return {\n text: chars.slice(-FAIL_CONFIRM_TAIL_LENGTH).join(\"\"),\n whole: false,\n };\n}\n",
7
+ "import type { JobDto } from \"../../api/types\";\nimport { useState } from \"react\";\nimport {\n FAIL_REASON_MAX_LENGTH,\n failJob,\n mutationInvalidation,\n} from \"../../api/jobs\";\nimport { ConfirmDialog } from \"../../components/ConfirmDialog\";\nimport { Field } from \"../../components/Field\";\nimport { TextInput } from \"../../components/inputs\";\nimport { useApiClient } from \"../../context\";\nimport { formatNumber } from \"../../format\";\nimport { useApiMutation } from \"../../hooks/useApiMutation\";\nimport { FAIL_CONFIRM_TAIL_LENGTH, failConfirmation } from \"./failConfirm\";\nimport { withFriendlyErrors } from \"./jobErrors\";\n\n/** Props of {@link FailJobDialog}. */\nexport interface FailJobDialogProps {\n /** The job to fail. */\n job: JobDto;\n /** Whether the dialog is open. The reason resets on each opening. */\n open: boolean;\n /** Called to close it: cancel, Escape, or after a successful fail. */\n onClose: () => void;\n}\n\n/** Fails a job for good (`POST /queues/:queue/jobs/:id/fail`), with a required reason. */\nexport function FailJobDialog(props: FailJobDialogProps) {\n return props.open ? <OpenFailJobDialog {...props} /> : null;\n}\n\n/** The mounted dialog; its state lives exactly as long as one opening. */\nfunction OpenFailJobDialog({ job, onClose }: FailJobDialogProps) {\n const api = useApiClient();\n const [reason, setReason] = useState(\"\");\n const fail = useApiMutation({\n mutationFn: async (text: string) => {\n const run = () => failJob(api, job.queue, job.id, text);\n return await withFriendlyErrors(\"fail\", run);\n },\n successMessage: \"Job failed: it is dead, with no attempts left\",\n invalidate: mutationInvalidation(job.queue),\n // The confirm dialog shows the failure in place.\n toastErrors: false,\n });\n // The API stores the reason exactly as sent, so it is sent as typed: it\n // needs a character other than whitespace (the schema's `\\S`) and at\n // most FAIL_REASON_MAX_LENGTH characters, spaces counted.\n const blank = !/\\S/.test(reason);\n const tooLong = reason.length > FAIL_REASON_MAX_LENGTH;\n const confirmation = failConfirmation(job.id);\n return (\n <ConfirmDialog\n open\n onClose={onClose}\n variant=\"danger\"\n title=\"Fail this job?\"\n description={\n <>\n Job <code>{job.id}</code> goes to dead for good, with your reason as\n its failure, whatever attempts it has left. It gets the{\" \"}\n <code>failed</code> and <code>dead</code> events and a copy in its\n dead-letter queue.\n </>\n }\n confirmLabel=\"Fail job\"\n pendingLabel=\"Failing…\"\n confirmText={confirmation.text}\n confirmTextLabel={\n confirmation.whole ? undefined : (\n <>\n Type the id's last {FAIL_CONFIRM_TAIL_LENGTH} characters,{\" \"}\n <code>{confirmation.text}</code>, to confirm\n </>\n )\n }\n confirmDisabled={blank || tooLong}\n onConfirm={() => fail.mutateAsync(reason)}\n >\n {job.state === \"active\" && (\n <p\n className=\"job-note job-note-warning\"\n data-testid=\"fail-active-note\"\n >\n This job is running. Failing it does not stop its code: its worker\n loses the job's lock at its next heartbeat and the attempt's signal is\n aborted, but the processor runs until it returns, and whatever it\n returns or throws is discarded.\n </p>\n )}\n <Field\n label=\"Reason\"\n required\n hint={`Recorded as the job's failure, exactly as typed. At most ${formatNumber(FAIL_REASON_MAX_LENGTH)} characters.`}\n error={\n fail.fieldErrors.reason ??\n (tooLong\n ? `At most ${formatNumber(FAIL_REASON_MAX_LENGTH)} characters.`\n : reason !== \"\" && blank\n ? \"Spaces alone are not a reason: type some text.\"\n : undefined)\n }\n >\n <TextInput\n value={reason}\n onChange={setReason}\n disabled={fail.isPending}\n />\n </Field>\n </ConfirmDialog>\n );\n}\n",
8
+ "import type { JobDto } from \"../../api/types\";\nimport { isFinished } from \"../../api/jobs\";\nimport { useCan, useFeature, useMeta } from \"../../meta/hooks\";\n\n/** Which of a job's actions this caller gets, and for which state. */\nexport interface JobActionGates {\n /** Retry: `jobs.retry`, and the job has finished. */\n retry: boolean;\n /** Promote: `jobs.promote`, and the job is delayed. */\n promote: boolean;\n /** Remove: `jobs.remove`. */\n remove: boolean;\n /** Update: `jobs.update` (an opt-in) and `features.update`. */\n update: boolean;\n /** Fail: `jobs.fail`, and the job is not finished for good (`completed` or `dead`). */\n fail: boolean;\n}\n\n/**\n * Whether a job in `state` can still be failed: the API buries a waiting,\n * delayed, retry-pending (`failed`) or `waiting-children` job, and an active\n * one; only a `completed` or `dead` job is past it.\n */\nexport function isFailable(state: JobDto[\"state\"]): boolean {\n return state !== \"completed\" && state !== \"dead\";\n}\n\n/** Works out {@link JobActionGates}; every action is off under `readOnly`. */\nexport function useJobActionGates(job: JobDto): JobActionGates {\n const { readOnly } = useMeta();\n const canRetry = useCan(\"jobs.retry\");\n const canPromote = useCan(\"jobs.promote\");\n const canRemove = useCan(\"jobs.remove\");\n const canUpdate = useCan(\"jobs.update\");\n const canFail = useCan(\"jobs.fail\");\n const updateFeature = useFeature(\"update\");\n return {\n retry: !readOnly && canRetry && isFinished(job.state),\n promote: !readOnly && canPromote && job.state === \"delayed\",\n remove: !readOnly && canRemove,\n update: !readOnly && canUpdate && updateFeature,\n fail: !readOnly && canFail && isFailable(job.state),\n };\n}\n",
9
+ "import type { JobState, UpdateJobBody } from \"../../api/types\";\nimport type { JsonEditorState } from \"../../components/jsonParse\";\n\n/** What the form holds. */\nexport interface UpdateJobForm {\n /** Whether the data is being replaced. */\n changeData: boolean;\n /** The data editor's state. */\n data: JsonEditorState;\n /** The new priority, or `undefined` to keep it. */\n priority: number | undefined;\n /** The new run time (epoch ms), or `undefined` to keep it. */\n runAt: number | undefined;\n /** Whether the change is conditional on the job's state. */\n conditional: boolean;\n /** The states it is conditional on. */\n onlyIn: JobState[];\n}\n\n/** Client-side problems with an {@link UpdateJobForm}, keyed like the API's field errors. */\nexport type UpdateJobErrors = Partial<\n Record<\"form\" | \"data\" | \"priority\" | \"runAt\" | \"onlyIn\", string>\n>;\n\n/**\n * Checks the form the way the API would, so the common mistakes never make a\n * round trip: at least one of data, priority or run time, valid JSON within\n * the size limit, a finite priority, and at least one state when the change\n * is conditional.\n */\nexport function validateUpdateForm(form: UpdateJobForm): UpdateJobErrors {\n const errors: UpdateJobErrors = {};\n if (\n !form.changeData &&\n form.priority === undefined &&\n form.runAt === undefined\n ) {\n errors.form = \"Change at least one of data, priority or run time.\";\n }\n if (form.changeData) {\n if (form.data.error) {\n errors.data = \"The data is not valid JSON.\";\n } else if (form.data.overLimit) {\n errors.data = \"The data is larger than the API accepts.\";\n } else if (form.data.value === undefined) {\n errors.data = \"Enter the new data as JSON (null is allowed).\";\n }\n }\n if (form.priority !== undefined && !Number.isFinite(form.priority)) {\n errors.priority = \"Enter a number.\";\n }\n if (form.conditional && form.onlyIn.length === 0) {\n errors.onlyIn = \"Pick at least one state, or untick the condition.\";\n }\n return errors;\n}\n\n/** The request body for a valid {@link UpdateJobForm}. */\nexport function updateBody(form: UpdateJobForm): UpdateJobBody {\n return {\n ...(form.changeData ? { data: form.data.value } : {}),\n ...(form.priority === undefined ? {} : { priority: form.priority }),\n ...(form.runAt === undefined ? {} : { runAt: form.runAt }),\n ...(form.conditional ? { onlyIn: form.onlyIn } : {}),\n };\n}\n",
10
+ "import type { FormEvent } from \"react\";\nimport type { JobDto, JobState, UpdateJobBody } from \"../../api/types\";\nimport type { UpdateJobForm } from \"./updateJobForm\";\nimport { useId, useState } from \"react\";\nimport { JOB_STATES } from \"../../api/contract\";\nimport { isApiError } from \"../../api/errors\";\nimport { mutationInvalidation, updateJob } from \"../../api/jobs\";\nimport { Button } from \"../../components/Button\";\nimport { Dialog } from \"../../components/Dialog\";\nimport { Field } from \"../../components/Field\";\nimport { Checkbox, DateTimeInput, NumberInput } from \"../../components/inputs\";\nimport { JsonEditor } from \"../../components/JsonEditor\";\nimport { jsonEditorState } from \"../../components/jsonParse\";\nimport { ProblemBanner } from \"../../components/ProblemBanner\";\nimport { useApiClient } from \"../../context\";\nimport { STATE_LABELS } from \"../../format\";\nimport { useApiMutation } from \"../../hooks/useApiMutation\";\nimport { useFieldProblems } from \"../../hooks/useFieldProblems\";\nimport { useMeta } from \"../../meta/hooks\";\nimport { withFriendlyErrors } from \"./jobErrors\";\nimport { updateBody, validateUpdateForm } from \"./updateJobForm\";\n\n/** Props of {@link UpdateJobDialog}. */\nexport interface UpdateJobDialogProps {\n /** The job being edited; its current values seed the form. */\n job: JobDto;\n /** Whether the dialog is open. The form resets on each opening. */\n open: boolean;\n /** Called to close it: cancel, Escape, or after a successful update. */\n onClose: () => void;\n}\n\n/** Edits a stored job's data, priority or run time (`PATCH /queues/:queue/jobs/:id`). */\nexport function UpdateJobDialog(props: UpdateJobDialogProps) {\n return props.open ? <OpenUpdateJobDialog {...props} /> : null;\n}\n\n/** The mounted dialog; its state lives exactly as long as one opening. */\nfunction OpenUpdateJobDialog({ job, onClose }: UpdateJobDialogProps) {\n const api = useApiClient();\n const { limits } = useMeta();\n const formId = useId();\n const maxBytes = limits.maxJobDataBytes;\n const [form, setForm] = useState<UpdateJobForm>(() => ({\n changeData: false,\n data: jsonEditorState(JSON.stringify(job.data ?? null, null, 2), {\n maxBytes,\n }),\n priority: undefined,\n runAt: undefined,\n conditional: false,\n onlyIn: [job.state],\n }));\n const [submitted, setSubmitted] = useState(false);\n const times = useFieldProblems<\"runAt\">();\n const update = useApiMutation({\n mutationFn: async (body: UpdateJobBody) => {\n const run = () => updateJob(api, job.queue, job.id, body);\n return await withFriendlyErrors(\"update\", run);\n },\n successMessage: \"Job updated\",\n invalidate: mutationInvalidation(job.queue),\n toastErrors: false,\n onSuccess: () => onClose(),\n });\n\n const clientErrors = submitted ? validateUpdateForm(form) : {};\n const serverErrors = update.fieldErrors;\n const fieldError = (key: \"data\" | \"priority\" | \"runAt\" | \"onlyIn\") =>\n (key === \"runAt\" ? times.problems.runAt : undefined) ??\n clientErrors[key] ??\n serverErrors[key];\n // A problem that is not a per-field VALIDATION issue is shown as a banner,\n // as is one with an issue no field shows (`unplacedIssues`).\n const bannerError =\n update.error &&\n !(\n isApiError(update.error) &&\n Object.keys(serverErrors).length > 0 &&\n update.unplacedIssues.length === 0\n )\n ? update.error\n : null;\n const bodyError = serverErrors.body;\n\n const patch = (next: Partial<UpdateJobForm>) =>\n setForm((current) => ({ ...current, ...next }));\n\n const onSubmit = (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setSubmitted(true);\n if (times.blocked || Object.keys(validateUpdateForm(form)).length > 0) {\n return;\n }\n update.mutate(updateBody(form));\n };\n\n const toggleState = (state: JobState, checked: boolean) =>\n setForm((current) => ({\n ...current,\n onlyIn: checked\n ? JOB_STATES.filter(\n (candidate) =>\n candidate === state || current.onlyIn.includes(candidate),\n )\n : current.onlyIn.filter((candidate) => candidate !== state),\n }));\n\n const close = () => {\n if (!update.isPending) {\n onClose();\n }\n };\n\n return (\n <Dialog\n open\n onClose={close}\n title=\"Edit job\"\n description=\"Change what you tick or fill in; everything else stays as it is.\"\n size=\"lg\"\n closeOnEscape={!update.isPending}\n closeOnBackdrop={!update.isPending}\n footer={\n <>\n <Button\n onClick={close}\n disabled={update.isPending}\n >\n Cancel\n </Button>\n <Button\n variant=\"primary\"\n type=\"submit\"\n form={formId}\n disabled={update.isPending || times.blocked}\n aria-busy={update.isPending || undefined}\n >\n {update.isPending ? \"Saving…\" : \"Save\"}\n </Button>\n </>\n }\n >\n <form\n id={formId}\n className=\"job-form\"\n onSubmit={onSubmit}\n noValidate\n >\n <Checkbox\n checked={form.changeData}\n onChange={(changeData) => patch({ changeData })}\n label=\"Replace the data\"\n />\n {form.changeData && (\n <Field\n label=\"Data\"\n hint=\"JSON; null is allowed.\"\n error={fieldError(\"data\")}\n >\n <JsonEditor\n value={form.data.text}\n onChange={(data) => patch({ data })}\n maxBytes={maxBytes}\n rows={8}\n />\n </Field>\n )}\n <Field\n label=\"Priority\"\n hint={`Lower runs first. Now ${job.priority}; leave empty to keep it.`}\n error={fieldError(\"priority\")}\n >\n <NumberInput\n value={form.priority}\n onChange={(priority) => patch({ priority })}\n step=\"any\"\n placeholder={String(job.priority)}\n />\n </Field>\n <Field\n label=\"Run at\"\n hint=\"Moves only a waiting or delayed job. Leave empty to keep it.\"\n error={fieldError(\"runAt\")}\n >\n <DateTimeInput\n value={form.runAt}\n onChange={(runAt) => patch({ runAt })}\n onProblem={(problem) => times.report(\"runAt\", problem)}\n />\n </Field>\n <Checkbox\n checked={form.conditional}\n onChange={(conditional) => patch({ conditional })}\n label=\"Only if the job is still in one of these states\"\n hint=\"The change is skipped (and reported) if the job has moved on.\"\n />\n {form.conditional && (\n <fieldset\n className=\"job-states-picker\"\n aria-invalid={Boolean(fieldError(\"onlyIn\")) || undefined}\n >\n <legend>Only in</legend>\n {JOB_STATES.map((state) => (\n <Checkbox\n key={state}\n checked={form.onlyIn.includes(state)}\n onChange={(checked) => toggleState(state, checked)}\n label={STATE_LABELS[state]}\n />\n ))}\n {fieldError(\"onlyIn\") && (\n <p className=\"field-error\">{fieldError(\"onlyIn\")}</p>\n )}\n </fieldset>\n )}\n {(clientErrors.form ?? bodyError) && (\n <p\n className=\"field-error job-form-error\"\n role=\"alert\"\n >\n {clientErrors.form ?? bodyError}\n </p>\n )}\n {bannerError && <ProblemBanner error={bannerError} />}\n </form>\n </Dialog>\n );\n}\n",
11
+ "import type { JobDto } from \"../../api/types\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useState } from \"react\";\nimport {\n jobKeys,\n mutationInvalidation,\n promoteJob,\n queueScreenPath,\n removeJob,\n retryJob,\n} from \"../../api/jobs\";\nimport { Button } from \"../../components/Button\";\nimport { ConfirmDialog } from \"../../components/ConfirmDialog\";\nimport { Checkbox } from \"../../components/inputs\";\nimport { useApiClient } from \"../../context\";\nimport { useApiMutation } from \"../../hooks/useApiMutation\";\nimport { useNavigate } from \"../../routing\";\nimport { FailJobDialog } from \"./FailJobDialog\";\nimport { withFriendlyErrors } from \"./jobErrors\";\nimport { useJobActionGates } from \"./jobGates\";\nimport { UpdateJobDialog } from \"./UpdateJobDialog\";\n\n/** The dialog currently open, if any. */\ntype OpenDialog = \"retry\" | \"remove\" | \"update\" | \"fail\" | null;\n\n/** The job's action buttons and their dialogs. Renders nothing when no action is allowed. */\nexport function JobActions({ job }: { job: JobDto }) {\n const api = useApiClient();\n const queryClient = useQueryClient();\n const navigate = useNavigate();\n const gates = useJobActionGates(job);\n const [open, setOpen] = useState<OpenDialog>(null);\n const [resetAttempts, setResetAttempts] = useState(true);\n const invalidate = mutationInvalidation(job.queue);\n\n const retry = useApiMutation({\n mutationFn: async (reset: boolean) => {\n const run = () => retryJob(api, job.queue, job.id, reset);\n return await withFriendlyErrors(\"retry\", run);\n },\n successMessage: \"Job retried: it is waiting to run again\",\n invalidate,\n // The confirm dialog shows the failure in place.\n toastErrors: false,\n });\n const promote = useApiMutation({\n mutationFn: () =>\n withFriendlyErrors(\"promote\", () => promoteJob(api, job.queue, job.id)),\n successMessage: \"Job promoted: it can run now\",\n errorTitle: \"Could not promote the job\",\n invalidate,\n });\n const remove = useApiMutation({\n mutationFn: () =>\n withFriendlyErrors(\"remove\", () => removeJob(api, job.queue, job.id)),\n successMessage: \"Job removed\",\n invalidate,\n toastErrors: false,\n onSuccess: async () => {\n // Drop the job's cache first, so the invalidation after this does not\n // re-read a job that no longer exists.\n await queryClient.cancelQueries({\n queryKey: jobKeys.job(job.queue, job.id),\n });\n queryClient.removeQueries({ queryKey: jobKeys.job(job.queue, job.id) });\n navigate(queueScreenPath(job.queue));\n },\n });\n\n if (\n !gates.retry &&\n !gates.promote &&\n !gates.remove &&\n !gates.update &&\n !gates.fail\n ) {\n return null;\n }\n return (\n <div\n className=\"job-actions\"\n role=\"group\"\n aria-label=\"Job actions\"\n >\n {gates.retry && (\n <Button\n onClick={() => {\n setResetAttempts(true);\n setOpen(\"retry\");\n }}\n >\n Retry\n </Button>\n )}\n {gates.promote && (\n <Button\n disabled={promote.isPending}\n aria-busy={promote.isPending || undefined}\n onClick={() => promote.mutate()}\n >\n {promote.isPending ? \"Promoting…\" : \"Promote\"}\n </Button>\n )}\n {gates.update && <Button onClick={() => setOpen(\"update\")}>Edit</Button>}\n {gates.fail && (\n <Button\n variant=\"danger\"\n onClick={() => setOpen(\"fail\")}\n >\n Fail…\n </Button>\n )}\n {gates.remove && (\n <Button\n variant=\"danger\"\n onClick={() => setOpen(\"remove\")}\n >\n Remove\n </Button>\n )}\n\n <ConfirmDialog\n open={open === \"retry\"}\n onClose={() => setOpen(null)}\n title=\"Retry this job?\"\n description=\"It goes back to waiting and runs again.\"\n confirmLabel=\"Retry\"\n pendingLabel=\"Retrying…\"\n onConfirm={() => retry.mutateAsync(resetAttempts)}\n >\n <Checkbox\n checked={resetAttempts}\n onChange={setResetAttempts}\n label=\"Reset attempts\"\n hint=\"Count attempts from zero again, so it gets its full allowance of retries.\"\n />\n </ConfirmDialog>\n\n <ConfirmDialog\n open={open === \"remove\"}\n onClose={() => setOpen(null)}\n variant=\"danger\"\n title=\"Remove this job?\"\n description={\n <>\n Job <code>{job.id}</code> and its logs are deleted. This cannot be\n undone.\n </>\n }\n confirmLabel=\"Remove\"\n pendingLabel=\"Removing…\"\n onConfirm={() => remove.mutateAsync()}\n />\n\n <FailJobDialog\n job={job}\n open={open === \"fail\"}\n onClose={() => setOpen(null)}\n />\n\n <UpdateJobDialog\n job={job}\n open={open === \"update\"}\n onClose={() => setOpen(null)}\n />\n </div>\n );\n}\n",
12
+ "import type { JobDto, JobState, QueueEventName } from \"../../api/types\";\nimport { QUEUE_EVENT_TYPES } from \"@kingsleyweb/bun-jobs/api/contract\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useCallback } from \"react\";\nimport { isFinished, jobKeys } from \"../../api/jobs\";\nimport {\n liveChannels,\n useLiveInvalidation,\n useLiveSubscription,\n usePollInterval,\n} from \"../../live\";\nimport { POLL_INTERVAL_MS } from \"../../queryClient\";\n\n/**\n * How the job screen stays fresh: events on the job's channel invalidate it\n * (see {@link useJobLive}), and it also polls — at the intervals below while\n * live updates are off, much slower while they are live. Log lines and a\n * child's progress announce nothing on this job's channel, so the logs and\n * the children table keep their own intervals either way.\n */\n\n/** How often an unfinished job is re-read, ms. */\nexport const JOB_POLL_MS = POLL_INTERVAL_MS;\n\n/** How often the log page is re-read while the job is active or waiting, ms. */\nexport const LOG_POLL_MS = 3_000;\n\n/** States during which new log lines can appear, so the log page refreshes. */\nconst LOGGING_STATES: ReadonlySet<JobState> = new Set<JobState>([\n \"active\",\n \"waiting\",\n]);\n\n/** The job's refetch interval: {@link JOB_POLL_MS} until it has finished, then none. */\nexport function jobRefetchInterval(\n state: JobState | undefined,\n): number | false {\n return state === undefined || isFinished(state) ? false : JOB_POLL_MS;\n}\n\n/** The logs' refetch interval: {@link LOG_POLL_MS} while the job is active or waiting, else none. */\nexport function logsRefetchInterval(\n state: JobState | undefined,\n): number | false {\n return state !== undefined && LOGGING_STATES.has(state) ? LOG_POLL_MS : false;\n}\n\n/** Whether the log page auto-refreshes for a job in `state`. */\nexport function logsFollow(state: JobState | undefined): boolean {\n return logsRefetchInterval(state) !== false;\n}\n\n/** Page sizes offered for logs; those above `limits.maxLogPage` are dropped by the pager. */\nexport const LOG_PAGE_SIZES: readonly number[] = [50, 100, 200, 500];\n\n/** The API's default log page: `min(100, maxLogPage)`. */\nexport function defaultLogLimit(maxLogPage: number): number {\n return Math.max(1, Math.min(100, maxLogPage));\n}\n\n/**\n * The job's refetch interval, given live updates: a function of its state\n * ({@link jobRefetchInterval}, relaxed by `usePollInterval` while live).\n */\nexport function useJobRefetchInterval(): (\n state: JobState | undefined,\n) => number | false {\n const poll = usePollInterval(JOB_POLL_MS);\n return useCallback(\n (state) => (jobRefetchInterval(state) === false ? false : poll),\n [poll],\n );\n}\n\n/** Job events that change nothing the job screen shows: progress is patched in place instead, and an add that added nothing is no change. */\nconst NOT_REFETCHED: ReadonlySet<QueueEventName> = new Set<QueueEventName>([\n \"progress\",\n \"duplicate\",\n \"throttled\",\n]);\n\n/** The events on a job's channel that refetch the job, its logs and its children. */\nexport const JOB_EVENTS: readonly QueueEventName[] = QUEUE_EVENT_TYPES.filter(\n (type) => !NOT_REFETCHED.has(type),\n);\n\n/**\n * Whether a `progress` event's value is one the wire declares: a number, or\n * a record of fields. Anything else (an array, `null`, a string from a\n * producer on an older version) refetches the job instead of being written\n * into it, so the screen shows what the store holds.\n */\nexport function isRunProgress(\n value: unknown,\n): value is number | Record<string, unknown> {\n return (\n (typeof value === \"number\" && Number.isFinite(value)) ||\n (typeof value === \"object\" && value !== null && !Array.isArray(value))\n );\n}\n\n/**\n * The job screen's live updates, on `queue/<q>/job/<id>`. A state change\n * (or a retry, clean, removal, debounce) invalidates the job's key, which is\n * the prefix of its logs, stack traces and children, so all of them refetch.\n * A `progress` event refetches nothing: its value is written into the cached\n * job directly.\n */\nexport function useJobLive(queue: string, id: string, enabled: boolean): void {\n const queryClient = useQueryClient();\n const channels = [liveChannels.job(queue, id)];\n useLiveInvalidation(channels, [jobKeys.job(queue, id)], {\n events: JOB_EVENTS,\n enabled,\n });\n useLiveSubscription({\n channels,\n events: [\"progress\"],\n enabled,\n onEvent: (event) => {\n if (event.kind !== \"queue\" || event.type !== \"progress\") {\n return;\n }\n const { progress } = event.payload;\n if (!isRunProgress(progress)) {\n void queryClient.invalidateQueries({\n queryKey: jobKeys.job(queue, id),\n exact: true,\n });\n return;\n }\n const patch = (job: JobDto | undefined) =>\n job ? { ...job, progress } : job;\n queryClient.setQueryData<JobDto>(jobKeys.job(queue, id), patch);\n },\n });\n}\n",
13
+ "import type { ChildDto, JobDto } from \"../../api/types\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { getJobChildren, jobKeys, jobScreenPath } from \"../../api/jobs\";\nimport { Badge } from \"../../components/Badge\";\nimport { Card } from \"../../components/Card\";\nimport { JsonView } from \"../../components/JsonView\";\nimport { ProblemBanner } from \"../../components/ProblemBanner\";\nimport { Spinner } from \"../../components/Spinner\";\nimport { StateBadge } from \"../../components/StateBadge\";\nimport { Table } from \"../../components/Table\";\nimport { useApiClient } from \"../../context\";\nimport { formatNumber, plural } from \"../../format\";\nimport { Link } from \"../../router\";\nimport { ErrorDetails } from \"./ErrorDetails\";\nimport { jobRefetchInterval } from \"./polling\";\n\n/** A link to another job's screen, showing `queue / id`. */\nfunction JobRefLink({ queue, id }: { queue: string; id: string }) {\n return (\n <Link\n to={jobScreenPath(queue, id)}\n className=\"job-ref\"\n >\n <span className=\"muted\">{queue}</span> / <code>{id}</code>\n </Link>\n );\n}\n\n/** A child's outcome: its recorded value, its ignored failure, or nothing yet. */\nfunction ChildOutcome({ child }: { child: ChildDto }) {\n if (child.failure) {\n return (\n <ErrorDetails\n error={child.failure}\n label={`Failure of ${child.id}`}\n />\n );\n }\n if (child.value !== undefined) {\n return (\n <JsonView\n value={child.value}\n label={`Value of ${child.id}`}\n expandDepth={1}\n />\n );\n }\n return <span className=\"muted\">—</span>;\n}\n\n/** The children table, from `GET …/children`. */\nfunction FlowChildren({ job }: { job: JobDto }) {\n const api = useApiClient();\n const children = useQuery({\n queryKey: jobKeys.children(job.queue, job.id),\n queryFn: ({ signal }) => getJobChildren(api, job.queue, job.id, signal),\n refetchInterval: jobRefetchInterval(job.state),\n });\n if (children.isPending) {\n return (\n <Spinner\n label=\"Loading the children\"\n showLabel\n />\n );\n }\n if (children.isError) {\n return (\n <ProblemBanner\n error={children.error}\n title=\"Could not load the children\"\n onRetry={() => void children.refetch()}\n />\n );\n }\n const data = children.data;\n return (\n <>\n <Table label=\"Children\">\n <thead>\n <tr>\n <th scope=\"col\">Child</th>\n <th scope=\"col\">State</th>\n <th scope=\"col\">Outcome</th>\n </tr>\n </thead>\n <tbody>\n {data.children.map((child) => (\n <tr\n key={`${child.queue}:${child.id}`}\n data-testid={`flow-child-${child.queue}:${child.id}`}\n >\n <th scope=\"row\">\n <JobRefLink\n queue={child.queue}\n id={child.id}\n />\n </th>\n <td>\n {child.job ? (\n <StateBadge state={child.job.state} />\n ) : (\n <Badge\n tone=\"warning\"\n title=\"The child is gone, or in a queue this API cannot reach\"\n >\n unreachable\n </Badge>\n )}\n </td>\n <td>\n <ChildOutcome child={child} />\n </td>\n </tr>\n ))}\n </tbody>\n </Table>\n {data.truncated && (\n <p\n className=\"notice\"\n role=\"note\"\n data-testid=\"flow-truncated\"\n >\n Showing the first {plural(data.children.length, \"child\", \"children\")}:\n this job has more than the API lists at once.\n </p>\n )}\n </>\n );\n}\n\n/** A job's place in a flow: its parent, pending count and children. */\nexport function JobFlow({ job }: { job: JobDto }) {\n const flow = job.flow;\n if (!flow) {\n return null;\n }\n return (\n <Card title=\"Flow\">\n <dl className=\"kv kv-grid job-flow-summary\">\n <div className=\"kv-row\">\n <dt className=\"kv-label\">Parent</dt>\n <dd className=\"kv-value\">\n {flow.parent ? (\n <JobRefLink\n queue={flow.parent.queue}\n id={flow.parent.id}\n />\n ) : (\n <span className=\"muted\">None: this is the root</span>\n )}\n </dd>\n </div>\n <div className=\"kv-row\">\n <dt className=\"kv-label\">Pending children</dt>\n <dd className=\"kv-value\">{formatNumber(flow.pending)}</dd>\n </div>\n </dl>\n {flow.children.length > 0 ? (\n <FlowChildren job={job} />\n ) : (\n <p className=\"muted\">This job has no children.</p>\n )}\n </Card>\n );\n}\n",
14
+ "import type { JobState } from \"../../api/types\";\n\n/** Why the button is disabled on an active job. */\nexport const CLEAR_LOGS_ACTIVE_REASON =\n \"Clear logs once the job finishes — a running job is still writing to them.\";\n\n/** Why the button is disabled on a job whose log is empty. */\nexport const CLEAR_LOGS_EMPTY_REASON = \"No lines to clear.\";\n\n/**\n * Why clearing is not possible now, or `null` when it is: an active job is\n * still writing its log (the API answers 409 `JOB_ACTIVE`), and an empty log\n * has nothing to clear.\n */\nexport function clearLogsBlocker(\n state: JobState,\n total: number | null,\n): string | null {\n if (state === \"active\") {\n return CLEAR_LOGS_ACTIVE_REASON;\n }\n if (total === 0) {\n return CLEAR_LOGS_EMPTY_REASON;\n }\n return null;\n}\n",
15
+ "import type { JobState } from \"../../api/types\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useId, useState } from \"react\";\nimport { isApiError } from \"../../api/errors\";\nimport { clearJobLogs, jobKeys } from \"../../api/jobs\";\nimport { Button } from \"../../components/Button\";\nimport { ConfirmDialog } from \"../../components/ConfirmDialog\";\nimport { useApiClient } from \"../../context\";\nimport { plural } from \"../../format\";\nimport { useApiMutation } from \"../../hooks/useApiMutation\";\nimport { useCanMutate } from \"../queues/gating\";\nimport { clearLogsBlocker } from \"./clearLogs\";\nimport { withFriendlyErrors } from \"./jobErrors\";\n\n/** Props of {@link ClearLogsButton}. */\nexport interface ClearLogsButtonProps {\n /** The job's queue. */\n queue: string;\n /** The job's id. */\n id: string;\n /** The job's state: an `active` job's log cannot be cleared (409 `JOB_ACTIVE`). */\n state: JobState;\n /** How many lines the log holds, from the last page read; `null` until the first page has loaded. */\n total: number | null;\n /** Called after a successful clear, e.g. to go back to the first page. */\n onCleared?: () => void;\n}\n\n/**\n * \"Clear logs…\" and its confirmation (`DELETE /queues/:queue/jobs/:id/logs`),\n * offered when `jobs.clearLogs` is granted on a writable API. It stays in\n * place, disabled with its reason shown, while the job is active or the log\n * is empty, so the action does not appear and vanish as the job moves.\n */\nexport function ClearLogsButton({\n queue,\n id,\n state,\n total,\n onCleared,\n}: ClearLogsButtonProps) {\n const api = useApiClient();\n const queryClient = useQueryClient();\n const canMutate = useCanMutate();\n const reasonId = useId();\n const [open, setOpen] = useState(false);\n const clear = useApiMutation({\n mutationFn: () =>\n withFriendlyErrors(\"clearLogs\", () => clearJobLogs(api, queue, id)),\n successMessage: (result) => `Cleared ${plural(result.removed, \"line\")}`,\n invalidate: [jobKeys.logsAll(queue, id)],\n toastErrors: false,\n onSuccess: () => onCleared?.(),\n onError: (error) => {\n // The job became active (or went away) since it was read: re-read it,\n // so the button shows why it is disabled.\n if (\n isApiError(error) &&\n (error.code === \"JOB_ACTIVE\" || error.code === \"JOB_NOT_FOUND\")\n ) {\n void queryClient.invalidateQueries({\n queryKey: jobKeys.job(queue, id),\n });\n }\n },\n });\n\n if (!canMutate(\"jobs.clearLogs\")) {\n return null;\n }\n const blocker = clearLogsBlocker(state, total);\n return (\n <span className=\"job-logs-clear\">\n <Button\n variant=\"danger\"\n size=\"sm\"\n disabled={blocker !== null || total === null}\n aria-describedby={blocker ? reasonId : undefined}\n onClick={() => setOpen(true)}\n >\n Clear logs…\n </Button>\n {blocker && (\n <span\n id={reasonId}\n className=\"muted job-logs-clear-reason\"\n data-testid=\"clear-logs-reason\"\n >\n {blocker}\n </span>\n )}\n <ConfirmDialog\n open={open}\n onClose={() => setOpen(false)}\n variant=\"danger\"\n title={`Clear the ${plural(total ?? 0, \"log line\")} of this job?`}\n description={\n <>\n Job <code>{id}</code>'s log is emptied, and its next line is\n numbered 1 again. The job itself, its state and its counters are\n untouched. This cannot be undone.\n </>\n }\n confirmLabel=\"Clear logs\"\n pendingLabel=\"Clearing…\"\n onConfirm={() => clear.mutateAsync()}\n />\n </span>\n );\n}\n",
16
+ "import type { LogsWindow } from \"../../api/jobs\";\nimport type { JobState, SortOrder } from \"../../api/types\";\nimport { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport { useId, useState } from \"react\";\nimport { getJobLogs, jobKeys } from \"../../api/jobs\";\nimport { Card } from \"../../components/Card\";\nimport { Select } from \"../../components/inputs\";\nimport { Pager } from \"../../components/Pager\";\nimport { ProblemBanner } from \"../../components/ProblemBanner\";\nimport { Spinner } from \"../../components/Spinner\";\nimport { useApiClient } from \"../../context\";\nimport { formatNumber } from \"../../format\";\nimport { useMeta } from \"../../meta/hooks\";\nimport { LogLines } from \"../logs/LogLines\";\nimport { ClearLogsButton } from \"./ClearLogsButton\";\nimport {\n defaultLogLimit,\n LOG_PAGE_SIZES,\n LOG_POLL_MS,\n logsFollow,\n logsRefetchInterval,\n} from \"./polling\";\n\n/** The order choices. */\nconst ORDER_OPTIONS = [\n { value: \"asc\", label: \"Oldest first\" },\n { value: \"desc\", label: \"Newest first\" },\n] as const satisfies readonly { value: SortOrder; label: string }[];\n\n/** Props of {@link JobLogs}. */\nexport interface JobLogsProps {\n /** The job's queue. */\n queue: string;\n /** The job's id. */\n id: string;\n /** The job's state: the page refreshes while it is active or waiting. */\n state: JobState;\n}\n\n/**\n * A job's log lines: paged (at most `limits.maxLogPage` a page), oldest or\n * newest first, refreshing every few seconds while the job can still log.\n * \"Clear logs…\" sits beside the order when `jobs.clearLogs` is granted.\n */\nexport function JobLogs({ queue, id, state }: JobLogsProps) {\n const api = useApiClient();\n const { limits } = useMeta();\n const orderId = useId();\n const [view, setView] = useState<LogsWindow>(() => ({\n offset: 0,\n limit: defaultLogLimit(limits.maxLogPage),\n order: \"asc\",\n }));\n const logs = useQuery({\n queryKey: jobKeys.logs(queue, id, view),\n queryFn: ({ signal }) => getJobLogs(api, queue, id, view, signal),\n placeholderData: keepPreviousData,\n refetchInterval: logsRefetchInterval(state),\n });\n const data = logs.data;\n const total = data?.page.total ?? 0;\n /** A line's 1-based number in the whole log, whichever the order. */\n const lineNumber = (index: number) =>\n view.order === \"asc\"\n ? view.offset + index + 1\n : total - view.offset - index;\n\n return (\n <Card\n title=\"Logs\"\n actions={\n <span className=\"job-logs-controls\">\n <span className=\"job-logs-order\">\n <label htmlFor={orderId}>Order</label>\n <Select\n id={orderId}\n options={ORDER_OPTIONS}\n value={view.order}\n onChange={(order) =>\n setView((current) => ({ ...current, order, offset: 0 }))\n }\n />\n </span>\n <ClearLogsButton\n queue={queue}\n id={id}\n state={state}\n total={data ? total : null}\n onCleared={() => setView((current) => ({ ...current, offset: 0 }))}\n />\n </span>\n }\n >\n {logsFollow(state) && (\n <p\n className=\"muted job-logs-follow\"\n data-testid=\"logs-follow\"\n >\n Refreshing every {LOG_POLL_MS / 1000} s while the job is {state}.\n </p>\n )}\n {!data ? (\n logs.isError ? (\n <ProblemBanner\n error={logs.error}\n title=\"Could not load the logs\"\n onRetry={() => void logs.refetch()}\n />\n ) : (\n <Spinner\n label=\"Loading the logs\"\n showLabel\n />\n )\n ) : (\n <>\n {logs.isError && (\n <ProblemBanner\n error={logs.error}\n title=\"Could not refresh the logs\"\n onRetry={() => void logs.refetch()}\n />\n )}\n {data.items.length === 0 ? (\n <p className=\"muted\">\n {total === 0 ? \"No log lines yet.\" : \"No lines on this page.\"}\n </p>\n ) : (\n <LogLines\n label=\"Log lines\"\n items={data.items.map((line, index) => ({\n // Lines are positional; the number is their identity.\n id: lineNumber(index),\n number: lineNumber(index),\n text: line,\n }))}\n />\n )}\n <Pager\n label=\"Log pages\"\n offset={view.offset}\n limit={view.limit}\n total={total}\n itemCount={data.items.length}\n hasMore={data.page.hasMore}\n pageSizes={LOG_PAGE_SIZES}\n maxPageSize={limits.maxLogPage}\n onChange={(next) => setView((current) => ({ ...current, ...next }))}\n />\n <p className=\"muted job-logs-total\">\n {formatNumber(total)} {total === 1 ? \"line\" : \"lines\"} in all\n </p>\n </>\n )}\n </Card>\n );\n}\n",
17
+ "import type { SyntheticEvent } from \"react\";\nimport type { ApiError } from \"../../api/errors\";\nimport type { JobDto, JobWorkerDto } from \"../../api/types\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { useState } from \"react\";\nimport { isApiError } from \"../../api/errors\";\nimport {\n DEFAULT_JOB_INCLUDE,\n getJob,\n jobKeys,\n queueScreenPath,\n} from \"../../api/jobs\";\nimport { workerPath } from \"../../api/workers\";\nimport { Card } from \"../../components/Card\";\nimport { CopyButton } from \"../../components/CopyButton\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { ErrorView } from \"../../components/ErrorView\";\nimport { JsonView } from \"../../components/JsonView\";\nimport { KeyValue } from \"../../components/KeyValue\";\nimport { ProblemBanner } from \"../../components/ProblemBanner\";\nimport { RelativeTime } from \"../../components/RelativeTime\";\nimport { Spinner } from \"../../components/Spinner\";\nimport { StateBadge } from \"../../components/StateBadge\";\nimport { useApiClient } from \"../../context\";\nimport { displayText, formatNumber } from \"../../format\";\nimport { useCan, useFeature, usePermissionsSettled } from \"../../meta/hooks\";\nimport { Link } from \"../../router\";\nimport { useParams } from \"../../routing\";\nimport { useWorkerPagesRouted } from \"../workers/routed\";\nimport { ErrorDetails } from \"./ErrorDetails\";\nimport { JobActions } from \"./JobActions\";\nimport { JobFlow } from \"./JobFlow\";\nimport { JobLogs } from \"./JobLogs\";\nimport { useJobLive, useJobRefetchInterval } from \"./polling\";\nimport \"./job.css\";\n\n/** Whether an error means the job (or its queue) does not exist. */\nfunction isNotFound(error: unknown): boolean {\n return (\n isApiError(error) &&\n (error.code === \"JOB_NOT_FOUND\" || error.code === \"QUEUE_NOT_FOUND\")\n );\n}\n\n/**\n * Whether an error means the caller may not read this job (401/403): the\n * host's `authorize` can decide per job, so the scoped permissions can say\n * yes while the request itself says no.\n */\nfunction isDenied(error: unknown): error is ApiError {\n return isApiError(error) && error.isAuth;\n}\n\n/** A progress number as text: up to two decimals, in the browser's locale. */\nconst progressFormat = new Intl.NumberFormat(undefined, {\n maximumFractionDigits: 2,\n});\n\n/**\n * The bar's fill for a progress number: a percentage clamped to 0–100, since\n * a processor may report anything (and `NaN` fills nothing).\n */\nfunction progressPercent(value: number): number {\n if (Number.isNaN(value)) {\n return 0;\n }\n return Math.min(100, Math.max(0, value));\n}\n\n/**\n * The job's progress, as `updateProgress()` reports it: a number is a\n * percentage, shown as a bar with its value; a record is a JSON tree. A store\n * may hold anything, so any other value is shown as text.\n */\nexport function ProgressValue({ progress }: { progress: unknown }) {\n if (progress === null || progress === undefined) {\n return null;\n }\n if (typeof progress === \"number\") {\n const percent = progressPercent(progress);\n const text = `${progressFormat.format(progress)}%`;\n return (\n <span\n className=\"job-progress\"\n data-testid=\"job-progress\"\n >\n <span\n className=\"job-progress-bar\"\n role=\"progressbar\"\n aria-label=\"Progress\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={percent}\n aria-valuetext={text}\n >\n <span\n className=\"job-progress-fill\"\n style={{ width: `${percent}%` }}\n />\n </span>\n <span className=\"job-progress-value\">{text}</span>\n </span>\n );\n }\n if (typeof progress === \"object\") {\n return (\n <JsonView\n value={progress}\n label=\"Progress\"\n expandDepth={1}\n copyable={false}\n />\n );\n }\n return <span>{String(progress)}</span>;\n}\n\n/** Props of {@link ProcessedBy}. */\ninterface ProcessedByProps {\n /** The job's queue: a worker page is addressed by it and the key. */\n queue: string;\n /** The worker that ran the job's last attempt. */\n worker: JobWorkerDto;\n}\n\n/**\n * Who ran a job's last attempt: the stable key (linked to its worker page\n * when the Workers pages are routed), the incarnation id, and host and pid\n * when the API exposes them.\n */\nfunction ProcessedBy({ queue, worker }: ProcessedByProps) {\n const routed = useWorkerPagesRouted();\n const where =\n worker.host !== undefined || worker.pid !== undefined\n ? [\n worker.host,\n worker.pid === undefined ? undefined : `pid ${worker.pid}`,\n ]\n .filter((part) => part !== undefined)\n .join(\", \")\n : null;\n return (\n <span\n className=\"job-processed-by\"\n data-testid=\"job-processed-by\"\n >\n {worker.key !== undefined &&\n (routed ? (\n <Link\n to={workerPath(queue, worker.key)}\n data-testid=\"job-processed-by-key\"\n >\n {worker.key}\n </Link>\n ) : (\n <span data-testid=\"job-processed-by-key\">{worker.key}</span>\n ))}\n <span className=\"muted\">\n {worker.key !== undefined ? \"incarnation \" : \"worker \"}\n <code>{worker.id}</code>\n </span>\n {where !== null && <span className=\"muted\">{where}</span>}\n </span>\n );\n}\n\n/** The job's state, attempts and timings. */\nexport function JobSummary({ job }: { job: JobDto }) {\n const attribution = useFeature(\"jobAttribution\");\n return (\n <KeyValue\n className=\"job-summary\"\n items={[\n { label: \"State\", value: <StateBadge state={job.state} /> },\n { label: \"Priority\", value: formatNumber(job.priority) },\n {\n label: \"Attempts\",\n value: `${formatNumber(job.attemptsMade)} of ${formatNumber(job.maxAttempts)}`,\n },\n { label: \"Stalled\", value: formatNumber(job.stalledCount) },\n { label: \"Run at\", value: <RelativeTime value={job.runAt} /> },\n { label: \"Created\", value: <RelativeTime value={job.createdAt} /> },\n { label: \"Processed\", value: <RelativeTime value={job.processedOn} /> },\n { label: \"Finished\", value: <RelativeTime value={job.finishedOn} /> },\n { label: \"Expires\", value: <RelativeTime value={job.expiresAt} /> },\n {\n label: \"Lock expires\",\n value: <RelativeTime value={job.lockExpiresAt} />,\n },\n // The holder, only while the attempt runs: not who ran a finished job.\n job.state === \"active\" && {\n label: \"Held by\",\n value: job.workerId ? <code>{job.workerId}</code> : null,\n },\n attribution && {\n label: \"Processed by\",\n key: \"processedBy\",\n value: job.processedBy ? (\n <ProcessedBy\n queue={job.queue}\n worker={job.processedBy}\n />\n ) : (\n <span data-testid=\"job-processed-by-none\">No worker recorded</span>\n ),\n hint: job.processedBy\n ? \"The worker that ran the last attempt; earlier attempts are not recorded.\"\n : \"The job was never claimed, or was claimed before this backend recorded who ran a job.\",\n },\n {\n label: \"Repeat key\",\n value: job.repeatKey ? <code>{job.repeatKey}</code> : null,\n },\n {\n label: \"Progress\",\n value: <ProgressValue progress={job.progress} />,\n key: \"progress\",\n },\n ]}\n />\n );\n}\n\n/** The failure history (`include=stacktrace`), fetched the first time its section opens. */\nexport function JobStacktraces({ queue, id }: { queue: string; id: string }) {\n const api = useApiClient();\n const [opened, setOpened] = useState(false);\n const traces = useQuery({\n queryKey: jobKeys.stacktrace(queue, id),\n queryFn: ({ signal }) => getJob(api, queue, id, [\"stacktrace\"], signal),\n enabled: opened,\n });\n const onToggle = (event: SyntheticEvent<HTMLDetailsElement>) => {\n if (event.currentTarget.open) {\n setOpened(true);\n }\n };\n const items = traces.data?.stacktrace ?? [];\n return (\n <details\n className=\"job-details\"\n onToggle={onToggle}\n data-testid=\"job-stacktraces\"\n >\n <summary>Failure history (newest first)</summary>\n {!opened ? null : traces.isPending ? (\n <Spinner\n label=\"Loading the failure history\"\n showLabel\n />\n ) : traces.isError ? (\n <ProblemBanner\n error={traces.error}\n title=\"Could not load the failure history\"\n onRetry={() => void traces.refetch()}\n />\n ) : items.length === 0 ? (\n <p className=\"muted\">No failures were kept for this job.</p>\n ) : (\n <ol className=\"job-stacktraces\">\n {items.map((trace, index) => (\n <li\n // Newest first; a trace has no identity beyond its position.\n // eslint-disable-next-line react/no-array-index-key\n key={index}\n >\n <ErrorDetails\n error={trace}\n label={index === 0 ? \"Newest failure\" : `Failure ${index + 1}`}\n />\n </li>\n ))}\n </ol>\n )}\n </details>\n );\n}\n\n/** Shown when the job, or its queue, does not exist. */\nexport function JobNotFound({ queue, id }: { queue: string; id: string }) {\n return (\n <div\n className=\"screen\"\n data-testid=\"job-not-found\"\n >\n <EmptyState\n headingLevel={1}\n title=\"Job not found\"\n description={\n <>\n There is no job <code>{id}</code> in queue <code>{queue}</code>. It\n may have finished and been cleaned up, or been removed.\n </>\n }\n action={<Link to={queueScreenPath(queue)}>Back to {queue}</Link>}\n />\n </div>\n );\n}\n\n/** Props of {@link JobHidden}. */\nexport interface JobHiddenProps {\n /** The queue the job is in; the panel links back to it. */\n queue: string;\n /** The API's own explanation (a 401/403 problem's `detail`), shown instead of the default sentence. */\n detail?: string;\n}\n\n/** Shown when the caller may not read this queue's jobs (`jobs.read`), or the API refused this one. */\nexport function JobHidden({ queue, detail }: JobHiddenProps) {\n return (\n <div\n className=\"screen\"\n data-testid=\"job-hidden\"\n >\n <EmptyState\n headingLevel={1}\n title=\"Job hidden\"\n description={\n detail ?? (\n <>\n You may not read the jobs of queue <code>{queue}</code>.\n </>\n )\n }\n action={<Link to={queueScreenPath(queue)}>Back to {queue}</Link>}\n />\n </div>\n );\n}\n\n/** The loaded job: header, actions and every section. */\nfunction JobDetail({ job }: { job: JobDto }) {\n const canLogs = useCan(\"jobs.logs\");\n const logsFeature = useFeature(\"logs\");\n const showReturn =\n job.returnValue !== undefined &&\n (job.state === \"completed\" || job.returnValue !== null);\n return (\n <>\n <header className=\"job-header\">\n <div className=\"job-heading\">\n <h1 className=\"screen-title job-title\">\n {/* Defence in depth: `getJob`'s shape guard already refuses a job\n whose name is not a string, so the \"(invalid)\" fallback is\n unreachable here. In practice only the unguarded list cells\n show it. */}\n <span className=\"job-name\">{displayText(job.name)}</span>\n <StateBadge state={job.state} />\n </h1>\n <p className=\"job-id-line\">\n <span className=\"muted\">Id</span>\n <code\n className=\"job-id\"\n data-testid=\"job-id\"\n >\n {job.id}\n </code>\n <CopyButton\n text={job.id}\n ariaLabel=\"Copy job id\"\n size=\"sm\"\n />\n </p>\n </div>\n <JobActions job={job} />\n </header>\n\n <Card title=\"Summary\">\n <JobSummary job={job} />\n </Card>\n\n {job.failedReason && (\n <Card title=\"Failure\">\n <ErrorDetails\n error={job.failedReason}\n label=\"Failure\"\n />\n <JobStacktraces\n queue={job.queue}\n id={job.id}\n />\n </Card>\n )}\n\n <Card title=\"Data\">\n {job.data === undefined ? (\n <p className=\"muted\">Not included in this response.</p>\n ) : (\n <JsonView\n value={job.data}\n label=\"Job data\"\n />\n )}\n </Card>\n\n {showReturn && (\n <Card title=\"Return value\">\n <JsonView\n value={job.returnValue}\n label=\"Return value\"\n />\n </Card>\n )}\n\n {job.flow && <JobFlow job={job} />}\n\n {logsFeature && canLogs && (\n <JobLogs\n queue={job.queue}\n id={job.id}\n state={job.state}\n />\n )}\n\n {job.opts && (\n <Card title=\"Options\">\n <JsonView\n value={job.opts}\n label=\"Job options\"\n expandDepth={1}\n />\n </Card>\n )}\n </>\n );\n}\n\n/**\n * `/queues/:queue/jobs/:id`: one job, polled until it finishes. Needs\n * `jobs.read` for the queue, as the queue's own map answers it: the job is\n * not fetched until that map has loaded, and never without `jobs.read` (or\n * after a 401/403), so a host that refuses one queue's jobs is never asked\n * for one.\n */\nexport function JobScreen() {\n const { queue = \"\", id = \"\" } = useParams<{ queue: string; id: string }>();\n const api = useApiClient();\n // Scoped to the queue by the route's <QueuePermissionScope>. The job waits\n // for that map (`settled`), so an untargeted grant cannot send a read the\n // queue's own answer refuses; a later `false` also stops the polling.\n const canRead = useCan(\"jobs.read\");\n const settled = usePermissionsSettled();\n const refetchInterval = useJobRefetchInterval();\n const job = useQuery({\n queryKey: jobKeys.job(queue, id),\n queryFn: ({ signal }) =>\n getJob(api, queue, id, DEFAULT_JOB_INCLUDE, signal),\n enabled: canRead && settled,\n refetchInterval: (query) =>\n isNotFound(query.state.error) || isDenied(query.state.error)\n ? false\n : refetchInterval(query.state.data?.state),\n });\n // Subscribes once the queue's map allows the read, and stops when the job\n // turns out to be refused or gone.\n useJobLive(\n queue,\n id,\n canRead && settled && !isDenied(job.error) && !isNotFound(job.error),\n );\n\n if (settled && !canRead) {\n return <JobHidden queue={queue} />;\n }\n if (isDenied(job.error)) {\n return (\n <JobHidden\n queue={queue}\n detail={job.error.detail}\n />\n );\n }\n if (isNotFound(job.error)) {\n return (\n <JobNotFound\n queue={queue}\n id={id}\n />\n );\n }\n return (\n <div\n className=\"screen job-screen\"\n data-testid=\"job-screen\"\n >\n <nav\n className=\"breadcrumb\"\n aria-label=\"Breadcrumb\"\n >\n <ol>\n <li>\n <Link to=\"/queues\">Queues</Link>\n </li>\n <li>\n <Link to={queueScreenPath(queue)}>{queue}</Link>\n </li>\n <li aria-current=\"page\">Job</li>\n </ol>\n </nav>\n {job.data ? (\n <>\n {job.isError && (\n <ProblemBanner\n error={job.error}\n title=\"Could not refresh this job\"\n onRetry={() => void job.refetch()}\n />\n )}\n <JobDetail job={job.data} />\n </>\n ) : job.isError ? (\n <ErrorView\n error={job.error}\n headingLevel={1}\n title=\"Could not load the job\"\n onRetry={() => void job.refetch()}\n />\n ) : (\n <Spinner\n label=\"Loading the job\"\n showLabel\n />\n )}\n </div>\n );\n}\n"
18
+ ],
19
+ "mappings": "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",
20
+ "debugId": "C598CA256C5DFC7B64756E2164756E21",
21
+ "names": []
22
+ }
@@ -0,0 +1,5 @@
1
+ import{r,Z,Lt,K,b,P,n,e,t,i,y,a,_,pe,Ke,as,z,l,M,V,S,Le}from"./main-a250ffk7.js";import{Ue,ct}from"./main-tnbhrm9n.js";var yr=["data","returnValue","opts"],C=new Set(["completed","failed","dead"]);function kn(o){return C.has(o)}var se={job:(o,s)=>[..._.queue(o),"job",s],stacktrace:(o,s)=>[...se.job(o,s),"stacktrace"],logsAll:(o,s)=>[...se.job(o,s),"logs"],logs:(o,s,d)=>[...se.logsAll(o,s),{offset:d.offset,limit:d.limit,order:d.order}],children:(o,s)=>[...se.job(o,s),"children"],definitions:()=>["definitions"]};function lt(o){return[_.queue(o),_.queuesAll,["overview"]]}function c(o,s){return`/queues/${P(o)}/jobs/${P(s)}`}function We(o,s){return`/queues/${encodeURIComponent(o)}/jobs/${encodeURIComponent(s)}`}function Mt(o){return`/queues/${encodeURIComponent(o)}`}function R(o){return Ke(o,"id","name","queue")&&as(o.state)}async function Er(o,s,d,u=yr,p){let g=c(s,d),A=await o.request("GET",g,{query:{include:u},signal:p});return pe(A,R,"a job",g)}function Xr(o,s,d,u,p){return o.request("GET",`${c(s,d)}/logs`,{query:{offset:u.offset,limit:u.limit,order:u.order},signal:p})}function eo(o,s,d){return o.request("DELETE",`${c(s,d)}/logs`)}function to(o,s,d,u){return o.request("GET",`${c(s,d)}/children`,{signal:u})}function no(o,s,d,u){return o.request("PATCH",c(s,d),{body:u})}async function ro(o,s,d){await o.request("DELETE",c(s,d))}function oo(o,s,d,u){return o.request("POST",`${c(s,d)}/retry`,{body:{resetAttempts:u}})}function so(o,s,d){return o.request("POST",`${c(s,d)}/promote`)}var xn=4096;function io(o,s,d,u){return o.request("POST",`${c(s,d)}/fail`,{body:{reason:u}})}function ao(o,s,d){return o.request("POST",`/queues/${P(s)}/jobs`,{body:d})}function uo(o,s,d){return o.request("POST",`/queues/${P(s)}/flows`,{body:d})}function lo(o){return[...[...new Set(o)].map((s)=>_.queue(s)),_.queuesAll,_.overviewAll]}function w(o,s){return o.request("GET","/definitions",{signal:s})}function on({addableNames:o,maxBytes:s}){return{name:o?.length===1?o[0]:"",data:Ue("{}",{maxBytes:s}),jobId:"",priority:void 0,timing:"now",delay:void 0,runAt:void 0,attempts:void 0,backoff:void 0,timeout:void 0}}function h(o,s){if(o===void 0)return;if(!Number.isInteger(o))return"Enter a whole number.";return o<s?`Enter ${s} or more.`:void 0}function br(o){if(o==="")return;if(o.length>Lt)return`At most ${Lt} characters (this one has ${o.length}).`;if(o.startsWith("."))return"An id cannot start with a dot.";if(/[\u0000-\u001F\u007F]/.test(o))return"An id cannot contain control characters.";return}function Ft(o){let s={};if(o.name.trim()==="")s.name="Pick or enter a job name.";else if(o.name.length>200)s.name="At most 200 characters.";if(o.data.error)s.data="The data is not valid JSON.";else if(o.data.overLimit)s.data="The data is larger than the API accepts.";else if(o.data.value===void 0)s.data="Enter the data as JSON (null is allowed).";let d=br(o.jobId);if(d)s["opts.jobId"]=d;if(o.priority!==void 0&&!Number.isFinite(o.priority))s["opts.priority"]="Enter a number.";if(o.timing==="delay")s["opts.delay"]=o.delay===void 0?"Enter the delay in milliseconds.":h(o.delay,0);if(o.timing==="runAt"&&o.runAt===void 0)s["opts.runAt"]="Pick when it should run.";s["opts.attempts"]=h(o.attempts,1),s["opts.backoff"]=h(o.backoff,0),s["opts.timeout"]=h(o.timeout,0);for(let u of Object.keys(s))if(s[u]===void 0)delete s[u];return s}function sn(o){let s={...o.jobId===""?{}:{jobId:o.jobId},...o.priority===void 0?{}:{priority:o.priority},...o.timing==="delay"&&o.delay!==void 0?{delay:o.delay}:{},...o.timing==="runAt"&&o.runAt!==void 0?{runAt:o.runAt}:{},...o.attempts===void 0?{}:{attempts:o.attempts},...o.backoff===void 0?{}:{backoff:o.backoff},...o.timeout===void 0?{}:{timeout:o.timeout}};return{name:o.name.trim(),data:o.data.value,...Object.keys(s).length>0?{opts:s}:{}}}var v={retry:"retried",promote:"promoted",remove:"removed",update:"updated",add:"added",fail:"failed",clearLogs:"cleared of its logs"},I={retry:"Only a completed, retrying, dead or delayed job can be retried.",promote:"Only a delayed job can be promoted.",remove:"Refresh to see where it is now.",update:"A new run time applies only to a waiting or delayed job, and “only in” limits the change to the states you picked.",add:"",fail:"Only a job that has not completed or died can be failed.",clearLogs:"Refresh to see where it is now."};function k(o){let s=o.context.state;return typeof s==="string"&&Z.includes(s)?s:null}function Ot(o,s){switch(o.code){case"JOB_ACTIVE":return s==="clearLogs"?"It's running: a worker picked this job up and is still writing its log, so the log cannot be cleared now. Clear it once the job finishes.":"It's running: a worker is processing this job right now, so it cannot be removed. Wait for it to finish, then try again.";case"JOB_STATE_CONFLICT":{let d=k(o);if(s==="fail"&&d==="active")return"A worker claimed the job again while it was being failed, so nothing changed. Refresh and try again.";return`${d?`The job is ${z[d].toLowerCase()} now`:"The job is in another state now"}, so it cannot be ${v[s]}. ${I[s]}`.trim()}case"JOB_NOT_FOUND":return"The job no longer exists: it finished and was cleaned up, or somebody removed it.";case"NAME_NOT_ADDABLE":{let d=o.context.name;return`Jobs named “${typeof d==="string"?d:"this"}” may not be added over this API. Pick one of the names it allows.`}case"SERIALIZATION":return"The data cannot be stored: it is not serialisable for this queue's backend.";default:return null}}function Nr(o,s){if(!b(o))return o;let d=Ot(o,s);if(d===null)return o;return new K({kind:o.kind,status:o.status,code:o.code,title:o.title,detail:d,type:o.type,issues:o.issues,context:o.context,instance:o.instance,cause:o})}async function Je(o,s){try{return await s()}catch(d){throw Nr(d,o)}}var O=[{value:"now",label:"Now"},{value:"delay",label:"After a delay"},{value:"runAt",label:"At a time"}],D="\x00pick",F="\x00other";function an({value:o,onChange:s,error:d,addableNames:u}){let p=i(),g=y("definitions.list"),A=u===null,T=a({queryKey:se.definitions(),queryFn:({signal:m})=>w(p,m),enabled:A&&g,staleTime:60000}),[E,x]=r(!1);if(!A)return e(l,{label:"Name",required:!0,error:d,children:e(S,{options:[{value:"",label:"Pick a name…",disabled:!0},...u.map((m)=>({value:m}))],value:o,onChange:s})});let f=T.data?.items.map((m)=>m.name)??[],J=E||f.length===0;return t(n,{children:[f.length>0&&e(l,{label:"Name",required:!0,hint:"Any name is accepted; the defined ones are listed.",error:J?void 0:d,children:e(S,{options:[{value:D,label:"Pick a name…",disabled:!0},...f.map((m)=>({value:m})),{value:F,label:"Another name…"}],value:J?F:f.includes(o)?o:D,onChange:(m)=>{if(m===F)x(!0),s("");else x(!1),s(m)}})}),J&&e(l,{label:f.length>0?"Another name":"Name",required:!0,hint:f.length>0?void 0:"Any name is accepted. A worker must handle it for the job to run.",error:d,children:e(M,{value:o,onChange:s,maxLength:200,autoComplete:"off",spellCheck:!1})})]})}function un({value:o,onChange:s,error:d,maxBytes:u,rows:p=8}){return e(l,{label:"Data",required:!0,hint:"JSON; null is allowed.",error:d,children:e(ct,{value:o,onChange:s,maxBytes:u,rows:p})})}function ln({form:o,patch:s,fieldError:d,onRunAtProblem:u}){return t("fieldset",{className:"job-form-options",children:[e("legend",{children:"Options"}),e(l,{label:"Job id",hint:`Optional, at most ${Lt} characters. An id that already exists returns that job instead.`,error:d("opts.jobId"),children:e(M,{value:o.jobId,onChange:(p)=>s({jobId:p}),maxLength:Lt,autoComplete:"off",spellCheck:!1})}),e(l,{label:"Priority",hint:"Lower runs first.",error:d("opts.priority"),children:e(V,{value:o.priority,onChange:(p)=>s({priority:p}),step:"any"})}),e(l,{label:"Run",children:e(S,{options:O,value:o.timing,onChange:(p)=>s({timing:p})})}),o.timing==="delay"&&e(l,{label:"Delay (ms)",required:!0,error:d("opts.delay"),children:e(V,{value:o.delay,onChange:(p)=>s({delay:p}),min:0})}),o.timing==="runAt"&&e(l,{label:"Run at",required:!0,error:d("opts.runAt"),children:e(Le,{value:o.runAt,onChange:(p)=>s({runAt:p}),onProblem:u})}),e(l,{label:"Attempts",hint:"Attempts allowed in total, 1 or more.",error:d("opts.attempts"),children:e(V,{value:o.attempts,onChange:(p)=>s({attempts:p}),min:1})}),e(l,{label:"Backoff (ms)",hint:"Fixed delay between attempts.",error:d("opts.backoff"),children:e(V,{value:o.backoff,onChange:(p)=>s({backoff:p}),min:0})}),e(l,{label:"Timeout (ms)",hint:"How long one attempt may run.",error:d("opts.timeout"),children:e(V,{value:o.timeout,onChange:(p)=>s({timeout:p}),min:0})})]})}
2
+ export{yr,kn,se,lt,We,Mt,Er,Xr,eo,to,no,ro,oo,so,xn,io,ao,uo,lo,on,br,Ft,sn,Ot,Nr,Je,an,un,ln};
3
+
4
+ //# debugId=D5FF077F8955C70A64756E2164756E21
5
+ //# sourceMappingURL=main-szjdmydc.js.map