railwatch 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/app/frontend/components/app-content.tsx +26 -0
  4. data/app/frontend/components/app-logo-icon.tsx +23 -0
  5. data/app/frontend/components/app-logo.tsx +14 -0
  6. data/app/frontend/components/app-shell.tsx +38 -0
  7. data/app/frontend/components/app-sidebar-header.tsx +54 -0
  8. data/app/frontend/components/app-sidebar.tsx +94 -0
  9. data/app/frontend/components/app-wordmark.tsx +29 -0
  10. data/app/frontend/components/breadcrumbs.tsx +46 -0
  11. data/app/frontend/components/icon.tsx +16 -0
  12. data/app/frontend/components/input-error.tsx +18 -0
  13. data/app/frontend/components/nav-footer.tsx +46 -0
  14. data/app/frontend/components/nav-main.tsx +41 -0
  15. data/app/frontend/components/nav-user.tsx +50 -0
  16. data/app/frontend/components/railwatch/chart-hover.tsx +34 -0
  17. data/app/frontend/components/railwatch/chart-panel.tsx +170 -0
  18. data/app/frontend/components/railwatch/code.tsx +28 -0
  19. data/app/frontend/components/railwatch/command-palette.tsx +132 -0
  20. data/app/frontend/components/railwatch/copy-block.tsx +52 -0
  21. data/app/frontend/components/railwatch/copy-id.tsx +49 -0
  22. data/app/frontend/components/railwatch/cursor-load-more.tsx +39 -0
  23. data/app/frontend/components/railwatch/data-table.tsx +195 -0
  24. data/app/frontend/components/railwatch/digit-roll.tsx +312 -0
  25. data/app/frontend/components/railwatch/empty-state.tsx +159 -0
  26. data/app/frontend/components/railwatch/env-nav.tsx +130 -0
  27. data/app/frontend/components/railwatch/env-switcher.tsx +145 -0
  28. data/app/frontend/components/railwatch/filter-bar.tsx +153 -0
  29. data/app/frontend/components/railwatch/flamegraph.tsx +324 -0
  30. data/app/frontend/components/railwatch/frames.tsx +295 -0
  31. data/app/frontend/components/railwatch/json-viewer.tsx +159 -0
  32. data/app/frontend/components/railwatch/live-dot.tsx +78 -0
  33. data/app/frontend/components/railwatch/live-toggle.tsx +76 -0
  34. data/app/frontend/components/railwatch/metric-chart.tsx +112 -0
  35. data/app/frontend/components/railwatch/mobile-page-switcher.tsx +89 -0
  36. data/app/frontend/components/railwatch/onboarding.tsx +91 -0
  37. data/app/frontend/components/railwatch/origin-identity.tsx +84 -0
  38. data/app/frontend/components/railwatch/page-header.tsx +42 -0
  39. data/app/frontend/components/railwatch/percentile-picker.tsx +18 -0
  40. data/app/frontend/components/railwatch/relative-time.tsx +18 -0
  41. data/app/frontend/components/railwatch/saved-views.tsx +232 -0
  42. data/app/frontend/components/railwatch/segmented.tsx +64 -0
  43. data/app/frontend/components/railwatch/series-chart.tsx +400 -0
  44. data/app/frontend/components/railwatch/shortcuts-dialog.tsx +89 -0
  45. data/app/frontend/components/railwatch/signal-toast.tsx +121 -0
  46. data/app/frontend/components/railwatch/sort-header.tsx +37 -0
  47. data/app/frontend/components/railwatch/source-link.tsx +57 -0
  48. data/app/frontend/components/railwatch/sparkline-cell.tsx +14 -0
  49. data/app/frontend/components/railwatch/sql-block.tsx +116 -0
  50. data/app/frontend/components/railwatch/stat.tsx +173 -0
  51. data/app/frontend/components/railwatch/status-badge.tsx +79 -0
  52. data/app/frontend/components/railwatch/step-picker.tsx +57 -0
  53. data/app/frontend/components/railwatch/timeline.tsx +405 -0
  54. data/app/frontend/components/railwatch/utilisation-bar.tsx +44 -0
  55. data/app/frontend/components/railwatch/window-picker.tsx +147 -0
  56. data/app/frontend/components/ui/avatar.tsx +51 -0
  57. data/app/frontend/components/ui/badge.tsx +46 -0
  58. data/app/frontend/components/ui/breadcrumb.tsx +109 -0
  59. data/app/frontend/components/ui/button.tsx +60 -0
  60. data/app/frontend/components/ui/card.tsx +92 -0
  61. data/app/frontend/components/ui/chart.tsx +374 -0
  62. data/app/frontend/components/ui/checkbox.tsx +32 -0
  63. data/app/frontend/components/ui/collapsible.tsx +31 -0
  64. data/app/frontend/components/ui/command.tsx +182 -0
  65. data/app/frontend/components/ui/dialog.tsx +158 -0
  66. data/app/frontend/components/ui/dropdown-menu.tsx +255 -0
  67. data/app/frontend/components/ui/hover-card.tsx +42 -0
  68. data/app/frontend/components/ui/input.tsx +21 -0
  69. data/app/frontend/components/ui/label.tsx +24 -0
  70. data/app/frontend/components/ui/popover.tsx +87 -0
  71. data/app/frontend/components/ui/select.tsx +185 -0
  72. data/app/frontend/components/ui/separator.tsx +28 -0
  73. data/app/frontend/components/ui/sheet.tsx +137 -0
  74. data/app/frontend/components/ui/sidebar.tsx +726 -0
  75. data/app/frontend/components/ui/skeleton.tsx +13 -0
  76. data/app/frontend/components/ui/sonner.tsx +41 -0
  77. data/app/frontend/components/ui/table.tsx +114 -0
  78. data/app/frontend/components/ui/tabs.tsx +91 -0
  79. data/app/frontend/components/ui/textarea.tsx +18 -0
  80. data/app/frontend/components/ui/tooltip.tsx +61 -0
  81. data/app/frontend/components/user-info.tsx +32 -0
  82. data/app/frontend/components/user-menu-content.tsx +94 -0
  83. data/app/frontend/entrypoints/application.css +244 -0
  84. data/app/frontend/entrypoints/inertia.tsx +95 -0
  85. data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
  86. data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
  87. data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
  88. data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
  89. data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
  90. data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
  91. data/app/frontend/hooks/use-appearance.tsx +67 -0
  92. data/app/frontend/hooks/use-clipboard.ts +32 -0
  93. data/app/frontend/hooks/use-flash.tsx +23 -0
  94. data/app/frontend/hooks/use-hotkeys.ts +63 -0
  95. data/app/frontend/hooks/use-initials.tsx +15 -0
  96. data/app/frontend/hooks/use-live.ts +179 -0
  97. data/app/frontend/hooks/use-mobile-navigation.ts +8 -0
  98. data/app/frontend/hooks/use-mobile.ts +21 -0
  99. data/app/frontend/hooks/use-percentile.ts +24 -0
  100. data/app/frontend/hooks/use-reduced-motion.ts +25 -0
  101. data/app/frontend/hooks/use-row-nav.ts +106 -0
  102. data/app/frontend/hooks/use-step.ts +27 -0
  103. data/app/frontend/hooks/use-ticker.ts +12 -0
  104. data/app/frontend/hooks/use-window.ts +58 -0
  105. data/app/frontend/layouts/app/app-sidebar-layout.tsx +107 -0
  106. data/app/frontend/layouts/app-layout.tsx +21 -0
  107. data/app/frontend/layouts/env-layout.tsx +53 -0
  108. data/app/frontend/layouts/persistent-layout.tsx +18 -0
  109. data/app/frontend/lib/cable.ts +10 -0
  110. data/app/frontend/lib/env-pages.ts +236 -0
  111. data/app/frontend/lib/execution-path.ts +55 -0
  112. data/app/frontend/lib/explain.ts +50 -0
  113. data/app/frontend/lib/filter.ts +25 -0
  114. data/app/frontend/lib/flamegraph.ts +155 -0
  115. data/app/frontend/lib/format.ts +71 -0
  116. data/app/frontend/lib/keyboard.ts +18 -0
  117. data/app/frontend/lib/source-link.ts +217 -0
  118. data/app/frontend/lib/utils.ts +6 -0
  119. data/app/frontend/pages/alerts/index.tsx +171 -0
  120. data/app/frontend/pages/broadcasts/index.tsx +171 -0
  121. data/app/frontend/pages/cache_events/index.tsx +127 -0
  122. data/app/frontend/pages/commands/index.tsx +143 -0
  123. data/app/frontend/pages/deploys/index.tsx +94 -0
  124. data/app/frontend/pages/deploys/show.tsx +309 -0
  125. data/app/frontend/pages/deprecations/index.tsx +176 -0
  126. data/app/frontend/pages/exceptions/index.tsx +271 -0
  127. data/app/frontend/pages/executions/show.tsx +821 -0
  128. data/app/frontend/pages/issues/index.tsx +399 -0
  129. data/app/frontend/pages/issues/show.tsx +1367 -0
  130. data/app/frontend/pages/jobs/index.tsx +381 -0
  131. data/app/frontend/pages/jobs/klass.tsx +215 -0
  132. data/app/frontend/pages/llm_calls/index.tsx +664 -0
  133. data/app/frontend/pages/logs/index.tsx +303 -0
  134. data/app/frontend/pages/mails/index.tsx +218 -0
  135. data/app/frontend/pages/notifications/index.tsx +161 -0
  136. data/app/frontend/pages/outgoing_requests/index.tsx +309 -0
  137. data/app/frontend/pages/overview/show.tsx +333 -0
  138. data/app/frontend/pages/people/index.tsx +102 -0
  139. data/app/frontend/pages/people/show.tsx +149 -0
  140. data/app/frontend/pages/processes/index.tsx +335 -0
  141. data/app/frontend/pages/profiles/index.tsx +110 -0
  142. data/app/frontend/pages/profiles/show.tsx +105 -0
  143. data/app/frontend/pages/queries/index.tsx +492 -0
  144. data/app/frontend/pages/queries/show.tsx +276 -0
  145. data/app/frontend/pages/releases/index.tsx +190 -0
  146. data/app/frontend/pages/releases/release-health.tsx +68 -0
  147. data/app/frontend/pages/releases/release-path.ts +25 -0
  148. data/app/frontend/pages/releases/show.tsx +262 -0
  149. data/app/frontend/pages/requests/index.tsx +235 -0
  150. data/app/frontend/pages/requests/route.tsx +159 -0
  151. data/app/frontend/pages/scheduled_tasks/index.tsx +220 -0
  152. data/app/frontend/pages/spans/index.tsx +229 -0
  153. data/app/frontend/pages/spans/show.tsx +220 -0
  154. data/app/frontend/pages/storage_ops/index.tsx +150 -0
  155. data/app/frontend/pages/tenants/index.tsx +236 -0
  156. data/app/frontend/pages/tenants/show.tsx +477 -0
  157. data/app/frontend/pages/tenants/tenant-path.ts +26 -0
  158. data/app/frontend/pages/thresholds/index.tsx +414 -0
  159. data/app/frontend/pages/traces/show.tsx +208 -0
  160. data/app/frontend/pages/transactions/index.tsx +220 -0
  161. data/app/frontend/pages/view_renders/index.tsx +241 -0
  162. data/app/frontend/pages/visits/index.tsx +321 -0
  163. data/app/frontend/public/icon.png +0 -0
  164. data/app/frontend/public/icon.svg +5 -0
  165. data/app/frontend/routes/index.d.ts +2136 -0
  166. data/app/frontend/routes/index.js +2112 -0
  167. data/app/frontend/types/globals.d.ts +9 -0
  168. data/app/frontend/types/index.ts +288 -0
  169. data/app/frontend/types/vite-env.d.ts +9 -0
  170. data/lib/railwatch/version.rb +1 -1
  171. data/lib/railwatch.rb +8 -3
  172. data/public/railwatch/assets/app-layout-LvxN9xSS.js +41 -0
  173. data/public/railwatch/assets/application-Cc5v_Vk9.css +1 -0
  174. data/public/railwatch/assets/{arrow-up-CLQ-7heQ.js → arrow-up-DG01k9AL.js} +1 -1
  175. data/public/railwatch/assets/{braces-rSYydpLY.js → braces-C3w7pfxS.js} +1 -1
  176. data/public/railwatch/assets/{card-DPjFKfen.js → card-Dpwlr0uY.js} +1 -1
  177. data/public/railwatch/assets/{chart-DWh7l8yM.js → chart-DJHv3R7W.js} +1 -1
  178. data/public/railwatch/assets/{chart-hover-CfoZUY4J.js → chart-hover-CEH-DtXO.js} +1 -1
  179. data/public/railwatch/assets/{chart-panel-CC49WTQL.js → chart-panel-CUdIzJvk.js} +1 -1
  180. data/public/railwatch/assets/code-BRZprwun.js +1 -0
  181. data/public/railwatch/assets/copy-id-CwhGGiwF.js +1 -0
  182. data/public/railwatch/assets/cursor-load-more-uF1_955Q.js +1 -0
  183. data/public/railwatch/assets/data-table-DYi-N0hm.js +1 -0
  184. data/public/railwatch/assets/env-layout-HdcOaPPX.js +1 -0
  185. data/public/railwatch/assets/{execution-path-FAuIzOXB.js → execution-path-CElXXFO_.js} +1 -1
  186. data/public/railwatch/assets/filter-bar-DEv0G_34.js +1 -0
  187. data/public/railwatch/assets/{flamegraph-EaGkP2NT.js → flamegraph-SKD9yxW9.js} +2 -2
  188. data/public/railwatch/assets/frames-CUpyJQDy.js +1 -0
  189. data/public/railwatch/assets/index-2W-ppwap.js +1 -0
  190. data/public/railwatch/assets/index-74ocuU5d.js +1 -0
  191. data/public/railwatch/assets/{index-C8Cggnbw.js → index-B8NA62Lz.js} +2 -2
  192. data/public/railwatch/assets/index-BBCEkLqp.js +1 -0
  193. data/public/railwatch/assets/index-BDcglJzX.js +1 -0
  194. data/public/railwatch/assets/index-B_KFb9LS.js +1 -0
  195. data/public/railwatch/assets/index-BqK_VKom.js +1 -0
  196. data/public/railwatch/assets/index-BxCXBHP2.js +1 -0
  197. data/public/railwatch/assets/index-C1jc1OoH.js +1 -0
  198. data/public/railwatch/assets/index-C7zREldp.js +1 -0
  199. data/public/railwatch/assets/index-CEetm4oC.js +1 -0
  200. data/public/railwatch/assets/index-CIaZkokF.js +1 -0
  201. data/public/railwatch/assets/index-CNH85TZp.js +1 -0
  202. data/public/railwatch/assets/index-Ce4SB5AB.js +1 -0
  203. data/public/railwatch/assets/index-CiRMSYMn.js +1 -0
  204. data/public/railwatch/assets/index-CjxXgOs-.js +1 -0
  205. data/public/railwatch/assets/index-ClL-PYEV.js +1 -0
  206. data/public/railwatch/assets/index-CteZ6fop.js +1 -0
  207. data/public/railwatch/assets/index-CvqfEExO.js +1 -0
  208. data/public/railwatch/assets/index-CxTLT-Qh.js +1 -0
  209. data/public/railwatch/assets/index-DJd3OUSd.js +1 -0
  210. data/public/railwatch/assets/index-DPD-rLp6.js +1 -0
  211. data/public/railwatch/assets/index-DYK_dps4.js +1 -0
  212. data/public/railwatch/assets/index-DrayARrI.js +1 -0
  213. data/public/railwatch/assets/index-HRXGir-0.js +1 -0
  214. data/public/railwatch/assets/index-UJOUALr_.js +1 -0
  215. data/public/railwatch/assets/index-d-3J7gQ5.js +1 -0
  216. data/public/railwatch/assets/index-oQrvBpOm.js +1 -0
  217. data/public/railwatch/assets/inertia-FoqYZAk8.js +97 -0
  218. data/public/railwatch/assets/json-viewer-CxqMojEm.js +1 -0
  219. data/public/railwatch/assets/klass-DEF-Vat5.js +1 -0
  220. data/public/railwatch/assets/origin-identity-Y6gvfQVR.js +1 -0
  221. data/public/railwatch/assets/{percentile-picker-CeQgllxD.js → percentile-picker-E2aUdLxx.js} +1 -1
  222. data/public/railwatch/assets/relative-time-DcJbkKlN.js +1 -0
  223. data/public/railwatch/assets/{release-health-uP-GF3_V.js → release-health-C8WSCDKD.js} +1 -1
  224. data/public/railwatch/assets/route-qO5vX1p-.js +1 -0
  225. data/public/railwatch/assets/{segmented-BeIe4uqk.js → segmented-BWee8uc9.js} +1 -1
  226. data/public/railwatch/assets/select-G7rNBzv0.js +1 -0
  227. data/public/railwatch/assets/series-chart-C5nXIUc5.js +1 -0
  228. data/public/railwatch/assets/show-BEWisLQw.js +1 -0
  229. data/public/railwatch/assets/show-BEa9PGu1.js +2 -0
  230. data/public/railwatch/assets/{show-YsNpqbcI.js → show-BiT_Aloc.js} +3 -3
  231. data/public/railwatch/assets/show-BpCO49d3.js +1 -0
  232. data/public/railwatch/assets/show-CH7yxt7R.js +1 -0
  233. data/public/railwatch/assets/show-CTO8Lvd2.js +1 -0
  234. data/public/railwatch/assets/show-DnYsDMUt.js +2 -0
  235. data/public/railwatch/assets/show-DyzHM527.js +1 -0
  236. data/public/railwatch/assets/show-Nfpxs8su.js +1 -0
  237. data/public/railwatch/assets/show-ddjUMo9G.js +1 -0
  238. data/public/railwatch/assets/show-ksjbSzDH.js +1 -0
  239. data/public/railwatch/assets/{sort-header-mEJUM3Av.js → sort-header-uOq5VlrB.js} +1 -1
  240. data/public/railwatch/assets/{sparkline-cell-Dwg7awwh.js → sparkline-cell-DgGij0cu.js} +1 -1
  241. data/public/railwatch/assets/stat-BTzNKTm2.js +1 -0
  242. data/public/railwatch/assets/status-badge-BDkOXa-C.js +1 -0
  243. data/public/railwatch/assets/{tenant-path-CQoP-BeF.js → tenant-path-DU0ezKSU.js} +1 -1
  244. data/public/railwatch/assets/{use-clipboard-DNefo-ky.js → use-clipboard-DoENmlWY.js} +1 -1
  245. data/public/railwatch/assets/{use-live-DMTuhKfB.js → use-live-CE_fN5_6.js} +1 -1
  246. data/public/railwatch/manifest.json +612 -1591
  247. metadata +242 -117
  248. data/public/railwatch/assets/app-layout-Zc0v-hYh.js +0 -1
  249. data/public/railwatch/assets/app-wordmark-BA_60AVb.js +0 -1
  250. data/public/railwatch/assets/appearance-CcfP9tZ7.js +0 -1
  251. data/public/railwatch/assets/application-BrN3Sz94.css +0 -1
  252. data/public/railwatch/assets/auth-layout-C05gEBIQ.js +0 -1
  253. data/public/railwatch/assets/badge-DRae8XwK.js +0 -1
  254. data/public/railwatch/assets/checkbox-DPkLUiwM.js +0 -1
  255. data/public/railwatch/assets/code-CLmYS6FU.js +0 -1
  256. data/public/railwatch/assets/copy-DaQMJWoT.js +0 -1
  257. data/public/railwatch/assets/copy-block-CGcXxp8J.js +0 -1
  258. data/public/railwatch/assets/copy-id-vBYHQwxg.js +0 -1
  259. data/public/railwatch/assets/cursor-load-more-Ddwxe5dZ.js +0 -1
  260. data/public/railwatch/assets/data-table-CzKTEE-O.js +0 -1
  261. data/public/railwatch/assets/edit-B1kmWkzd.js +0 -8
  262. data/public/railwatch/assets/edit-D9cx4pbG.js +0 -1
  263. data/public/railwatch/assets/edit-yv6j9p-V.js +0 -1
  264. data/public/railwatch/assets/empty-state-CW4wclK_.js +0 -1
  265. data/public/railwatch/assets/env-layout-Kz7wks1x.js +0 -1
  266. data/public/railwatch/assets/filter-bar-CJDjWFib.js +0 -1
  267. data/public/railwatch/assets/frames-zZuIaNH9.js +0 -1
  268. data/public/railwatch/assets/google-sign-in-button-2_zgbgVy.js +0 -1
  269. data/public/railwatch/assets/index-B49SWz7K.js +0 -1
  270. data/public/railwatch/assets/index-BHlY4wKe.js +0 -1
  271. data/public/railwatch/assets/index-BZpPtyFY.js +0 -1
  272. data/public/railwatch/assets/index-BeVK6jCL.js +0 -1
  273. data/public/railwatch/assets/index-BfbSo01U.js +0 -1
  274. data/public/railwatch/assets/index-BfgncAv6.js +0 -1
  275. data/public/railwatch/assets/index-BhNszK1k.js +0 -1
  276. data/public/railwatch/assets/index-Bu01uWvw.js +0 -1
  277. data/public/railwatch/assets/index-C1s_hK3p.js +0 -1
  278. data/public/railwatch/assets/index-CBip6V4z.js +0 -1
  279. data/public/railwatch/assets/index-CMJGss5R.js +0 -1
  280. data/public/railwatch/assets/index-CRo3yK20.js +0 -1
  281. data/public/railwatch/assets/index-CWB_p2J8.js +0 -1
  282. data/public/railwatch/assets/index-CWHpndGc.js +0 -1
  283. data/public/railwatch/assets/index-Ca_S4Sc3.js +0 -1
  284. data/public/railwatch/assets/index-CanPDDOa.js +0 -1
  285. data/public/railwatch/assets/index-Cie90Yat.js +0 -1
  286. data/public/railwatch/assets/index-CiepQ_pR.js +0 -1
  287. data/public/railwatch/assets/index-Cm1uCIGN.js +0 -1
  288. data/public/railwatch/assets/index-CzitnnSC.js +0 -1
  289. data/public/railwatch/assets/index-DEUfClv3.js +0 -1
  290. data/public/railwatch/assets/index-DJKwo-mI.js +0 -1
  291. data/public/railwatch/assets/index-DK6y0YHp.js +0 -1
  292. data/public/railwatch/assets/index-DMNPpLH9.js +0 -1
  293. data/public/railwatch/assets/index-DZO1mSfX.js +0 -1
  294. data/public/railwatch/assets/index-Db5wj3M5.js +0 -1
  295. data/public/railwatch/assets/index-Dcy5WktB.js +0 -1
  296. data/public/railwatch/assets/index-DvG0-7Lx.js +0 -1
  297. data/public/railwatch/assets/index-Dvj1wuka.js +0 -1
  298. data/public/railwatch/assets/index-KyZX46qX.js +0 -1
  299. data/public/railwatch/assets/index-Ze-KP-sl.js +0 -1
  300. data/public/railwatch/assets/index-mhRbWLBM.js +0 -1
  301. data/public/railwatch/assets/index-x099JL5f.js +0 -1
  302. data/public/railwatch/assets/index-x28zb_nf.js +0 -1
  303. data/public/railwatch/assets/inertia-Cuyz2ZHO.js +0 -97
  304. data/public/railwatch/assets/input-error-hog6gGxg.js +0 -1
  305. data/public/railwatch/assets/json-viewer-DH2W9HXf.js +0 -1
  306. data/public/railwatch/assets/klass-DHbDelLk.js +0 -1
  307. data/public/railwatch/assets/label-DdCBgiUn.js +0 -1
  308. data/public/railwatch/assets/layout-Cueyl7c5.js +0 -1
  309. data/public/railwatch/assets/live-dot-BZgYTYdt.js +0 -41
  310. data/public/railwatch/assets/nav-BSSGObDZ.js +0 -1
  311. data/public/railwatch/assets/new-B8FSb8Bl.js +0 -1
  312. data/public/railwatch/assets/new-C39_v2Ll.js +0 -1
  313. data/public/railwatch/assets/new-DVOPwaC5.js +0 -1
  314. data/public/railwatch/assets/new-Dd40nvJR.js +0 -1
  315. data/public/railwatch/assets/new-SxnYe1SE.js +0 -1
  316. data/public/railwatch/assets/new-eP5vKD3Y.js +0 -1
  317. data/public/railwatch/assets/onboarding-CUpZl5KB.js +0 -1
  318. data/public/railwatch/assets/origin-identity-BYt2uiuo.js +0 -1
  319. data/public/railwatch/assets/relative-time-D5UbF4oO.js +0 -1
  320. data/public/railwatch/assets/route-DQAY8JEr.js +0 -1
  321. data/public/railwatch/assets/select-2R16457Z.js +0 -1
  322. data/public/railwatch/assets/separator-D5I0UCB5.js +0 -1
  323. data/public/railwatch/assets/series-chart-gnrzhmM6.js +0 -1
  324. data/public/railwatch/assets/show-2BkeNRUC.js +0 -2
  325. data/public/railwatch/assets/show-B0X1hRQH.js +0 -1
  326. data/public/railwatch/assets/show-BKUV5l5q.js +0 -1
  327. data/public/railwatch/assets/show-BQJD_CsF.js +0 -6
  328. data/public/railwatch/assets/show-BhmD6Sxp.js +0 -1
  329. data/public/railwatch/assets/show-C3KcnVo2.js +0 -1
  330. data/public/railwatch/assets/show-C95cHd06.js +0 -2
  331. data/public/railwatch/assets/show-CdB4uVQS.js +0 -1
  332. data/public/railwatch/assets/show-CoCqcVIp.js +0 -1
  333. data/public/railwatch/assets/show-D2LqjEsU.js +0 -1
  334. data/public/railwatch/assets/show-DNpnKyTh.js +0 -1
  335. data/public/railwatch/assets/show-DOlTxbig.js +0 -1
  336. data/public/railwatch/assets/show-DQCkttL8.js +0 -1
  337. data/public/railwatch/assets/show-DcxesipC.js +0 -1
  338. data/public/railwatch/assets/show-IGJoc_X0.js +0 -1
  339. data/public/railwatch/assets/show-qNV6H8SH.js +0 -2
  340. data/public/railwatch/assets/stat-ZtxGU8lE.js +0 -1
  341. data/public/railwatch/assets/status-badge-CH5P-Xkj.js +0 -1
  342. data/public/railwatch/assets/text-link-DHJ8BXx5.js +0 -1
  343. data/public/railwatch/assets/textarea-BeQtQyl5.js +0 -1
  344. data/public/railwatch/assets/timeline-CaKQVu48.js +0 -1
  345. data/public/railwatch/assets/transition-ksDpqhKJ.js +0 -5
@@ -0,0 +1,821 @@
1
+ import { Link, usePage } from "@inertiajs/react"
2
+ import { Check, ClipboardCopy, Copy } from "lucide-react"
3
+ import { useState } from "react"
4
+
5
+ import { DataTable } from "@/components/railwatch/data-table"
6
+ import { Flamegraph } from "@/components/railwatch/flamegraph"
7
+ import { ExceptionCard } from "@/components/railwatch/frames"
8
+ import { JsonViewer } from "@/components/railwatch/json-viewer"
9
+ import { OriginIdentity } from "@/components/railwatch/origin-identity"
10
+ import { PageHeader } from "@/components/railwatch/page-header"
11
+ import { SqlBlock } from "@/components/railwatch/sql-block"
12
+ import { Stat, StatStrip } from "@/components/railwatch/stat"
13
+ import { LevelBadge, StatusBadge } from "@/components/railwatch/status-badge"
14
+ import { Timeline } from "@/components/railwatch/timeline"
15
+ import { Badge } from "@/components/ui/badge"
16
+ import { Button } from "@/components/ui/button"
17
+ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
18
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
19
+ import { useClipboard } from "@/hooks/use-clipboard"
20
+ import EnvLayout from "@/layouts/env-layout"
21
+ import { executionPath } from "@/lib/execution-path"
22
+ import { bytes, count, ms, when } from "@/lib/format"
23
+ import * as R from "@/routes"
24
+ import type { ExceptionDetail, SharedProps, TimelineEntry } from "@/types"
25
+
26
+ interface Execution {
27
+ execution_id: string
28
+ kind: string
29
+ name: string
30
+ duration: number
31
+ status: number | null
32
+ outcome: string | null
33
+ method: string | null
34
+ route: string | null
35
+ controller: string | null
36
+ action: string | null
37
+ queue: string | null
38
+ attempt: number | null
39
+ queue_latency: number | null
40
+ queue_time: number | null
41
+ job_id: string | null
42
+ task_key: string | null
43
+ inertia_component: string | null
44
+ occurred_at: string
45
+ deploy: string | null
46
+ server: string | null
47
+ user_ref: string | null
48
+ tenant: string | null
49
+ trace_id: string
50
+ allocations: number | null
51
+ peak_memory: number | null
52
+ exception_preview: string | null
53
+ profiled: boolean
54
+ stages: Record<string, number>
55
+ counters: Record<string, number>
56
+ detail: Record<string, unknown>
57
+ }
58
+ interface Attachment {
59
+ id: number
60
+ name: string
61
+ content_type: string | null
62
+ bytes: number
63
+ truncated: boolean
64
+ viewable: boolean
65
+ occurred_at: string
66
+ }
67
+ interface Props {
68
+ execution: Execution
69
+ timeline: TimelineEntry[]
70
+ exceptions: ExceptionDetail[]
71
+ logs: {
72
+ id: number
73
+ level: string
74
+ message: string
75
+ tags: string[]
76
+ occurred_at: string
77
+ stage: string | null
78
+ }[]
79
+ queries: {
80
+ id: number
81
+ sql: string
82
+ duration: number
83
+ source: string | null
84
+ group_hash: string
85
+ row_count: number | null
86
+ stage: string | null
87
+ offset: number
88
+ }[]
89
+ spans: {
90
+ id: number
91
+ name: string
92
+ duration: number | null
93
+ status: string | null
94
+ attributes: Record<string, unknown>
95
+ offset: number
96
+ stage: string | null
97
+ occurred_at: string
98
+ }[]
99
+ profile: {
100
+ id: number
101
+ profiler: string
102
+ mode: string | null
103
+ interval: number | null
104
+ duration: number
105
+ samples: number
106
+ stacks_bytes: number | null
107
+ collapsed: string
108
+ truncated: boolean
109
+ } | null
110
+ attachments: Attachment[]
111
+ enqueued_jobs: {
112
+ id: number
113
+ name: string
114
+ queue: string
115
+ job_id: string
116
+ offset: number
117
+ attempt_execution_id: string | null
118
+ attempt_outcome: string | null
119
+ }[]
120
+ parent: { execution_id: string; kind: string; name: string } | null
121
+ trace: {
122
+ execution_id: string
123
+ kind: string
124
+ name: string
125
+ duration: number
126
+ status: number | null
127
+ outcome: string | null
128
+ occurred_at: string
129
+ parent_id: string | null
130
+ server: string | null
131
+ current: boolean
132
+ }[]
133
+ trace_url: string | null
134
+ issues: {
135
+ id: number
136
+ key: string
137
+ title: string
138
+ status: string
139
+ group_hash: string
140
+ }[]
141
+ person: { ref: string; name: string | null; email: string | null } | null
142
+ }
143
+
144
+ const kindLabel: Record<string, string> = {
145
+ request: "Request",
146
+ job_attempt: "Job attempt",
147
+ scheduled_task: "Scheduled task",
148
+ command: "Command",
149
+ channel_action: "Channel action",
150
+ }
151
+
152
+ // Tiny local copy-to-clipboard affordance for an inline id. CopyId doesn't
153
+ // exist yet (owned elsewhere), so this stays private to this page.
154
+ function CopyId({ value }: { value: string }) {
155
+ const [, copy] = useClipboard()
156
+ const [copied, setCopied] = useState(false)
157
+ return (
158
+ <button
159
+ type="button"
160
+ onClick={() => {
161
+ void (async () => {
162
+ const ok = await copy(value)
163
+ if (ok) {
164
+ setCopied(true)
165
+ setTimeout(() => setCopied(false), 1200)
166
+ }
167
+ })()
168
+ }}
169
+ className="hover:text-foreground text-muted-foreground ml-1 inline-flex align-middle"
170
+ title="Copy"
171
+ >
172
+ {copied ? <Check className="size-3" /> : <Copy className="size-3" />}
173
+ </button>
174
+ )
175
+ }
176
+
177
+ function parseContext(v: unknown): unknown {
178
+ try {
179
+ return JSON.parse(String(v)) as unknown
180
+ } catch {
181
+ return String(v)
182
+ }
183
+ }
184
+
185
+ function markdownReport(p: Props, x: Execution) {
186
+ const lines: string[] = []
187
+ lines.push(`# ${x.name}`)
188
+ lines.push("")
189
+ lines.push(`- status: ${x.status ?? x.outcome ?? "-"}`)
190
+ lines.push(`- duration: ${ms(x.duration)}`)
191
+ lines.push(`- occurred_at: ${x.occurred_at}`)
192
+ lines.push("")
193
+ lines.push("## Stages")
194
+ for (const [k, v] of Object.entries(x.stages)) lines.push(`- ${k}: ${ms(v)}`)
195
+ lines.push("")
196
+ lines.push("## Counters")
197
+ for (const [k, v] of Object.entries(x.counters)) lines.push(`- ${k}: ${v}`)
198
+ if (p.exceptions.length > 0) {
199
+ lines.push("")
200
+ lines.push("## Exceptions")
201
+ for (const ex of p.exceptions) {
202
+ lines.push(`### ${ex.class_name}: ${ex.message}`)
203
+ for (const f of ex.frames.filter((fr) => fr.in_app)) {
204
+ lines.push(`- ${f.file}:${f.line} in \`${f.function}\``)
205
+ }
206
+ }
207
+ }
208
+ const slow = [...p.queries]
209
+ .sort((a, b) => b.duration - a.duration)
210
+ .slice(0, 5)
211
+ if (slow.length > 0) {
212
+ lines.push("")
213
+ lines.push("## Slow queries")
214
+ for (const q of slow) lines.push(`- ${ms(q.duration, 2)} — \`${q.sql}\``)
215
+ }
216
+ return lines.join("\n")
217
+ }
218
+
219
+ export default function ExecutionShow(p: Props) {
220
+ const { environment, range, window } = usePage<SharedProps>().props
221
+ const a = environment!.application_id
222
+ const e = environment!.id
223
+ const x = p.execution
224
+ const detail = x.detail as Record<string, never>
225
+ const headers = (detail.headers ?? {}) as Record<string, string>
226
+ const inertia = detail.inertia as Record<string, unknown> | undefined
227
+ const [, copyMd] = useClipboard()
228
+ const [mdCopied, setMdCopied] = useState(false)
229
+ return (
230
+ <EnvLayout
231
+ title={x.name}
232
+ crumbs={[
233
+ { title: kindLabel[x.kind] ?? x.kind, href: "#" },
234
+ { title: x.name, href: "#" },
235
+ ]}
236
+ >
237
+ <PageHeader
238
+ withWindow={false}
239
+ title={
240
+ <span className="flex flex-wrap items-center gap-2">
241
+ <StatusBadge status={x.status} outcome={x.outcome} />
242
+ <span className="font-mono">{x.name}</span>
243
+ {x.profiled && (
244
+ <Badge variant="secondary" className="font-mono">
245
+ profiled
246
+ </Badge>
247
+ )}
248
+ </span>
249
+ }
250
+ description={
251
+ <span className="flex flex-wrap gap-x-3 text-xs">
252
+ <span>{when(x.occurred_at)}</span>
253
+ {x.server && <span>server {x.server}</span>}
254
+ {x.deploy && <span>deploy {x.deploy.slice(0, 12)}</span>}
255
+ {x.user_ref && (
256
+ <span className="inline-flex items-center gap-1">
257
+ origin user
258
+ <OriginIdentity
259
+ applicationId={a}
260
+ environmentId={e}
261
+ kind="user"
262
+ range={range}
263
+ window={window}
264
+ user_ref={x.user_ref}
265
+ tenant={x.tenant}
266
+ person={
267
+ p.person && {
268
+ ref: p.person.ref,
269
+ name: p.person.name ?? p.person.ref,
270
+ }
271
+ }
272
+ />
273
+ </span>
274
+ )}
275
+ {x.tenant && (
276
+ <span className="inline-flex items-center gap-1">
277
+ origin tenant
278
+ <OriginIdentity
279
+ applicationId={a}
280
+ environmentId={e}
281
+ kind="tenant"
282
+ range={range}
283
+ window={window}
284
+ user_ref={x.user_ref}
285
+ tenant={x.tenant}
286
+ person={null}
287
+ />
288
+ </span>
289
+ )}
290
+ </span>
291
+ }
292
+ actions={
293
+ <Button
294
+ type="button"
295
+ variant="outline"
296
+ size="sm"
297
+ onClick={() => {
298
+ void (async () => {
299
+ const ok = await copyMd(markdownReport(p, x))
300
+ if (ok) {
301
+ setMdCopied(true)
302
+ setTimeout(() => setMdCopied(false), 1200)
303
+ }
304
+ })()
305
+ }}
306
+ >
307
+ {mdCopied ? (
308
+ <Check className="size-3.5" />
309
+ ) : (
310
+ <ClipboardCopy className="size-3.5" />
311
+ )}
312
+ Copy as Markdown
313
+ </Button>
314
+ }
315
+ />
316
+ {p.issues.length > 0 && (
317
+ <div className="flex flex-wrap gap-2">
318
+ {p.issues.map((i) => (
319
+ <Link key={i.id} href={R.issuePath(i.id)}>
320
+ <Badge variant="destructive" className="font-mono">
321
+ {i.key}
322
+ </Badge>
323
+ </Link>
324
+ ))}
325
+ </div>
326
+ )}
327
+ <StatStrip>
328
+ <Stat
329
+ label="Duration"
330
+ value={ms(x.duration)}
331
+ hint={
332
+ x.queue_latency != null
333
+ ? `queued ${ms(x.queue_latency)}`
334
+ : undefined
335
+ }
336
+ />
337
+ <Stat
338
+ label="Queries"
339
+ value={x.counters.queries ?? 0}
340
+ hint={`${x.counters.cached_queries ?? 0} cached · ${x.counters.hydrated_models ?? 0} models`}
341
+ />
342
+ {x.queue_time != null && (
343
+ <Stat label="Queue time" value={ms(x.queue_time)} />
344
+ )}
345
+ <Stat label="Cache" value={x.counters.cache_events ?? 0} />
346
+ <Stat
347
+ label="Outgoing"
348
+ value={x.counters.outgoing_requests ?? 0}
349
+ hint={`${x.counters.jobs_enqueued ?? 0} jobs enqueued · ${x.counters.mail ?? 0} mail`}
350
+ />
351
+ <Stat
352
+ label="Allocations"
353
+ value={count(x.allocations)}
354
+ hint={x.peak_memory ? `RSS ${bytes(x.peak_memory)}` : undefined}
355
+ />
356
+ <Stat
357
+ label="Exceptions"
358
+ value={x.counters.exceptions ?? 0}
359
+ tone={x.counters.exceptions ? "destructive" : undefined}
360
+ hint={
361
+ x.counters.lazy_loads
362
+ ? `${x.counters.lazy_loads} lazy loads`
363
+ : undefined
364
+ }
365
+ />
366
+ </StatStrip>
367
+ {p.trace.length > 1 && (
368
+ <Card>
369
+ <CardHeader>
370
+ <CardTitle className="flex flex-wrap items-center justify-between gap-2">
371
+ Trace
372
+ {p.trace_url && (
373
+ <Link
374
+ href={p.trace_url}
375
+ className="text-primary text-xs font-normal hover:underline"
376
+ >
377
+ View distributed trace
378
+ </Link>
379
+ )}
380
+ </CardTitle>
381
+ </CardHeader>
382
+ <CardContent>
383
+ <div className="flex flex-wrap items-center gap-2 text-xs">
384
+ {p.trace.map((t, i) => {
385
+ const maxDur = Math.max(...p.trace.map((tt) => tt.duration), 1)
386
+ return (
387
+ <span
388
+ key={t.execution_id}
389
+ className="flex items-center gap-2"
390
+ >
391
+ {i > 0 && <span className="text-muted-foreground">→</span>}
392
+ <Link
393
+ href={executionPath({
394
+ applicationId: a,
395
+ environmentId: e,
396
+ source: t.kind,
397
+ executionId: t.execution_id,
398
+ })!}
399
+ className={
400
+ t.current
401
+ ? "bg-muted flex items-center gap-1.5 rounded px-2 py-1 font-semibold"
402
+ : "hover:bg-muted flex items-center gap-1.5 rounded px-2 py-1"
403
+ }
404
+ >
405
+ <StatusBadge status={t.status} outcome={t.outcome} />{" "}
406
+ <span className="font-mono">{t.name}</span>
407
+ <span className="bg-muted-foreground/25 relative h-1.5 w-10 overflow-hidden rounded-full">
408
+ <span
409
+ className="bg-primary absolute inset-y-0 left-0 rounded-full"
410
+ style={{
411
+ width: `${Math.max(4, (t.duration / maxDur) * 100)}%`,
412
+ }}
413
+ />
414
+ </span>
415
+ <span className="text-muted-foreground">
416
+ {ms(t.duration)}
417
+ </span>
418
+ </Link>
419
+ </span>
420
+ )
421
+ })}
422
+ </div>
423
+ </CardContent>
424
+ </Card>
425
+ )}
426
+ {p.exceptions.map((ex) => (
427
+ <Card key={ex.id} className="border-destructive/40">
428
+ <CardHeader>
429
+ <CardTitle>Exception</CardTitle>
430
+ </CardHeader>
431
+ <CardContent>
432
+ <ExceptionCard exception={ex} deploy={p.execution.deploy} />
433
+ </CardContent>
434
+ </Card>
435
+ ))}
436
+ <Card>
437
+ <CardHeader>
438
+ <CardTitle>Timeline</CardTitle>
439
+ </CardHeader>
440
+ <CardContent>
441
+ <Timeline entries={p.timeline} total={x.duration} stages={x.stages} />
442
+ </CardContent>
443
+ </Card>
444
+ {p.spans.length > 0 && (
445
+ <Card>
446
+ <CardHeader>
447
+ <CardTitle>Spans</CardTitle>
448
+ </CardHeader>
449
+ <CardContent>
450
+ <DataTable
451
+ rows={p.spans}
452
+ rowKey={(s) => s.id}
453
+ columns={[
454
+ {
455
+ key: "off",
456
+ header: "+ms",
457
+ align: "right",
458
+ cell: (s) => s.offset.toFixed(1),
459
+ },
460
+ {
461
+ key: "name",
462
+ header: "Span",
463
+ cell: (s) => (
464
+ <span className="font-mono text-xs">{s.name}</span>
465
+ ),
466
+ },
467
+ {
468
+ key: "status",
469
+ header: "Status",
470
+ cell: (s) => <StatusBadge outcome={s.status} />,
471
+ },
472
+ {
473
+ key: "attrs",
474
+ header: "Attributes",
475
+ cell: (s) => (
476
+ <span className="flex flex-wrap gap-1">
477
+ {Object.entries(s.attributes).map(([key, value]) => (
478
+ <span
479
+ key={key}
480
+ className="bg-muted rounded-sm px-1.5 font-mono text-[10px]"
481
+ >
482
+ {key}={String(value)}
483
+ </span>
484
+ ))}
485
+ </span>
486
+ ),
487
+ },
488
+ {
489
+ key: "dur",
490
+ header: "Duration",
491
+ align: "right",
492
+ cell: (s) => ms(s.duration, 2),
493
+ },
494
+ ]}
495
+ />
496
+ </CardContent>
497
+ </Card>
498
+ )}
499
+ {p.profile && (
500
+ <Card>
501
+ <CardHeader>
502
+ <CardTitle className="flex flex-wrap items-center justify-between gap-2">
503
+ Profile
504
+ <Link
505
+ href={R.applicationEnvironmentProfilePath(a, e, p.profile.id)}
506
+ className="text-primary text-xs font-normal hover:underline"
507
+ >
508
+ Open full profile
509
+ </Link>
510
+ </CardTitle>
511
+ </CardHeader>
512
+ <CardContent className="space-y-2">
513
+ <p className="text-muted-foreground font-mono text-[11px]">
514
+ {p.profile.profiler}
515
+ {p.profile.mode ? ` · ${p.profile.mode}` : ""} ·{" "}
516
+ {count(p.profile.samples)} samples · {ms(p.profile.duration)}
517
+ {p.profile.truncated && " · truncated for this page"}
518
+ </p>
519
+ <Flamegraph collapsed={p.profile.collapsed} />
520
+ </CardContent>
521
+ </Card>
522
+ )}
523
+ {p.attachments.length > 0 && (
524
+ <Card>
525
+ <CardHeader>
526
+ <CardTitle>Attachments</CardTitle>
527
+ </CardHeader>
528
+ <CardContent>
529
+ <DataTable
530
+ rows={p.attachments}
531
+ rowKey={(f) => f.id}
532
+ columns={[
533
+ {
534
+ key: "name",
535
+ header: "Name",
536
+ className: "max-w-0",
537
+ cell: (f) => (
538
+ <span className="flex items-center gap-1.5">
539
+ <span className="truncate font-mono text-xs">
540
+ {f.name}
541
+ </span>
542
+ {f.truncated && (
543
+ <Badge variant="secondary">truncated</Badge>
544
+ )}
545
+ </span>
546
+ ),
547
+ },
548
+ {
549
+ key: "type",
550
+ hideOnMobile: true,
551
+ header: "Type",
552
+ cell: (f) => (
553
+ <span className="text-muted-foreground font-mono text-xs">
554
+ {f.content_type ?? "–"}
555
+ </span>
556
+ ),
557
+ },
558
+ {
559
+ key: "size",
560
+ header: "Size",
561
+ align: "right",
562
+ cell: (f) => bytes(f.bytes),
563
+ },
564
+ {
565
+ key: "links",
566
+ header: "",
567
+ align: "right",
568
+ cell: (f) => (
569
+ <span className="flex justify-end gap-3">
570
+ {f.viewable && (
571
+ <a
572
+ className="text-primary hover:underline"
573
+ href={R.applicationEnvironmentAttachmentPath(
574
+ a,
575
+ e,
576
+ f.id,
577
+ { view: 1 },
578
+ )}
579
+ >
580
+ View
581
+ </a>
582
+ )}
583
+ <a
584
+ className="text-primary hover:underline"
585
+ href={R.applicationEnvironmentAttachmentPath(
586
+ a,
587
+ e,
588
+ f.id,
589
+ )}
590
+ >
591
+ Download
592
+ </a>
593
+ </span>
594
+ ),
595
+ },
596
+ ]}
597
+ />
598
+ </CardContent>
599
+ </Card>
600
+ )}
601
+ <Tabs defaultValue="queries">
602
+ <TabsList>
603
+ <TabsTrigger value="queries">
604
+ Queries ({p.queries.length})
605
+ </TabsTrigger>
606
+ <TabsTrigger value="logs">Logs ({p.logs.length})</TabsTrigger>
607
+ <TabsTrigger value="jobs">
608
+ Enqueued jobs ({p.enqueued_jobs.length})
609
+ </TabsTrigger>
610
+ <TabsTrigger value="request">Details</TabsTrigger>
611
+ </TabsList>
612
+ <TabsContent value="queries">
613
+ <DataTable
614
+ rows={p.queries}
615
+ rowKey={(q) => q.id}
616
+ empty="No queries."
617
+ columns={[
618
+ {
619
+ key: "off",
620
+ header: "+ms",
621
+ align: "right",
622
+ cell: (q) => q.offset.toFixed(1),
623
+ },
624
+ {
625
+ key: "sql",
626
+ header: "SQL",
627
+ cell: (q) => (
628
+ <Link
629
+ href={R.applicationEnvironmentQueryPath(a, e, q.group_hash)}
630
+ >
631
+ <SqlBlock sql={q.sql} className="hover:underline" />
632
+ </Link>
633
+ ),
634
+ },
635
+ {
636
+ key: "rows",
637
+ header: "Rows",
638
+ align: "right",
639
+ cell: (q) => q.row_count ?? "–",
640
+ },
641
+ {
642
+ key: "src",
643
+ header: "Source",
644
+ cell: (q) => (
645
+ <span className="text-muted-foreground font-mono text-xs">
646
+ {q.source ?? ""}
647
+ </span>
648
+ ),
649
+ },
650
+ {
651
+ key: "dur",
652
+ header: "Duration",
653
+ align: "right",
654
+ cell: (q) => ms(q.duration, 2),
655
+ },
656
+ ]}
657
+ />
658
+ </TabsContent>
659
+ <TabsContent value="logs">
660
+ <DataTable
661
+ rows={p.logs}
662
+ rowKey={(l) => l.id}
663
+ empty="No logs."
664
+ columns={[
665
+ {
666
+ key: "lvl",
667
+ header: "Level",
668
+ cell: (l) => <LevelBadge level={l.level} />,
669
+ },
670
+ {
671
+ key: "msg",
672
+ header: "Message",
673
+ cell: (l) => (
674
+ <span className="font-mono text-xs whitespace-pre-wrap">
675
+ {l.message}
676
+ </span>
677
+ ),
678
+ },
679
+ { key: "tags", header: "Tags", cell: (l) => l.tags.join(" ") },
680
+ {
681
+ key: "stage",
682
+ header: "Stage",
683
+ cell: (l) => (
684
+ <span className="text-muted-foreground text-xs">
685
+ {l.stage ?? ""}
686
+ </span>
687
+ ),
688
+ },
689
+ ]}
690
+ />
691
+ </TabsContent>
692
+ <TabsContent value="jobs">
693
+ <DataTable
694
+ rows={p.enqueued_jobs}
695
+ rowKey={(j) => j.id}
696
+ empty="No jobs enqueued."
697
+ columns={[
698
+ {
699
+ key: "name",
700
+ header: "Job",
701
+ cell: (j) => (
702
+ <span className="font-mono text-xs">{j.name}</span>
703
+ ),
704
+ },
705
+ { key: "queue", header: "Queue", cell: (j) => j.queue },
706
+ {
707
+ key: "id",
708
+ header: "Job id",
709
+ cell: (j) => (
710
+ <span className="font-mono text-xs">{j.job_id}</span>
711
+ ),
712
+ },
713
+ {
714
+ key: "attempt",
715
+ header: "Attempt",
716
+ cell: (j) =>
717
+ j.attempt_execution_id ? (
718
+ <Link
719
+ className="hover:underline"
720
+ href={R.applicationEnvironmentJobPath(
721
+ a,
722
+ e,
723
+ j.attempt_execution_id,
724
+ )}
725
+ >
726
+ <StatusBadge outcome={j.attempt_outcome} />
727
+ </Link>
728
+ ) : (
729
+ <span className="text-muted-foreground text-xs">
730
+ not seen yet
731
+ </span>
732
+ ),
733
+ },
734
+ ]}
735
+ />
736
+ </TabsContent>
737
+ <TabsContent value="request">
738
+ <div className="grid gap-4 lg:grid-cols-2">
739
+ <Card>
740
+ <CardHeader>
741
+ <CardTitle>Execution</CardTitle>
742
+ </CardHeader>
743
+ <CardContent>
744
+ <dl className="grid gap-y-1 text-xs sm:grid-cols-[10rem_1fr]">
745
+ {Object.entries({
746
+ kind: x.kind,
747
+ trace_id: x.trace_id,
748
+ execution_id: x.execution_id,
749
+ controller: x.controller && `${x.controller}#${x.action}`,
750
+ route: x.route,
751
+ url: detail.url,
752
+ ip: detail.ip,
753
+ format: detail.format,
754
+ queue: x.queue,
755
+ attempt: x.attempt,
756
+ job_id: x.job_id,
757
+ task_key: x.task_key,
758
+ schedule: detail.schedule,
759
+ inertia_component: x.inertia_component,
760
+ redirect_to: detail.redirect_to,
761
+ halted_callback: detail.halted_callback,
762
+ view_runtime: detail.view_runtime,
763
+ db_runtime: detail.db_runtime,
764
+ user_agent: detail.user_agent,
765
+ arguments: Array.isArray(detail.arguments_preview)
766
+ ? (detail.arguments_preview as string[]).join(", ")
767
+ : undefined,
768
+ })
769
+ .filter(
770
+ ([, v]) => v !== null && v !== undefined && v !== "",
771
+ )
772
+ .map(([k, v]) => (
773
+ <div key={k} className="contents">
774
+ <dt className="text-muted-foreground">{k}</dt>
775
+ <dd className="font-mono break-all">
776
+ {String(v)}
777
+ {(k === "trace_id" || k === "execution_id") && (
778
+ <CopyId value={String(v)} />
779
+ )}
780
+ </dd>
781
+ </div>
782
+ ))}
783
+ </dl>
784
+ {inertia && (
785
+ <div className="mt-3">
786
+ <JsonViewer data={inertia} />
787
+ </div>
788
+ )}
789
+ {detail.context && detail.context !== "{}" && (
790
+ <div className="mt-3">
791
+ <JsonViewer data={parseContext(detail.context)} />
792
+ </div>
793
+ )}
794
+ </CardContent>
795
+ </Card>
796
+ <Card>
797
+ <CardHeader>
798
+ <CardTitle>Headers</CardTitle>
799
+ </CardHeader>
800
+ <CardContent>
801
+ <dl className="grid gap-y-1 text-xs sm:grid-cols-[12rem_1fr]">
802
+ {Object.entries(headers).map(([k, v]) => (
803
+ <div key={k} className="contents">
804
+ <dt className="text-muted-foreground font-mono">{k}</dt>
805
+ <dd className="font-mono break-all">{v}</dd>
806
+ </div>
807
+ ))}
808
+ </dl>
809
+ {detail.payload != null && (
810
+ <div className="mt-3">
811
+ <JsonViewer data={detail.payload} />
812
+ </div>
813
+ )}
814
+ </CardContent>
815
+ </Card>
816
+ </div>
817
+ </TabsContent>
818
+ </Tabs>
819
+ </EnvLayout>
820
+ )
821
+ }