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,170 @@
1
+ import type { ReactNode } from "react"
2
+
3
+ import {
4
+ LatencyChart,
5
+ ThroughputChart,
6
+ } from "@/components/railwatch/series-chart"
7
+ import { count, ms } from "@/lib/format"
8
+ import { cn } from "@/lib/utils"
9
+ import type { DeployMarker, Percentile, SeriesPoint } from "@/types"
10
+
11
+ // Nightwatch's chart card: a mono label with the headline number at the
12
+ // left, a colour-keyed legend with per-series totals at the right, the
13
+ // chart below, and the bucket range under it (drawn by the chart itself).
14
+ export function ChartPanel({
15
+ label,
16
+ value,
17
+ legend,
18
+ children,
19
+ className,
20
+ }: {
21
+ label: string
22
+ value: ReactNode
23
+ legend?: { key: string; label: string; value: ReactNode; color: string }[]
24
+ children: ReactNode
25
+ className?: string
26
+ }) {
27
+ return (
28
+ <div className={cn("bg-card rounded-lg border p-4", className)}>
29
+ <div className="mb-3 flex items-start justify-between gap-4">
30
+ <div className="min-w-0">
31
+ <div className="label-caps">{label}</div>
32
+ <div className="mt-0.5 text-xl font-semibold tracking-tight tabular-nums">
33
+ {value}
34
+ </div>
35
+ </div>
36
+ {legend && (
37
+ <div className="flex shrink-0 gap-4">
38
+ {legend.map((l) => (
39
+ <div key={l.key} className="text-right">
40
+ <div className="label-caps flex items-center justify-end gap-1.5">
41
+ <span
42
+ className="inline-block h-2.5 w-1 rounded-sm"
43
+ style={{ background: l.color }}
44
+ />
45
+ {l.label}
46
+ </div>
47
+ <div className="mt-0.5 font-mono text-sm tabular-nums">
48
+ {l.value}
49
+ </div>
50
+ </div>
51
+ ))}
52
+ </div>
53
+ )}
54
+ </div>
55
+ {children}
56
+ </div>
57
+ )
58
+ }
59
+
60
+ function sum(data: SeriesPoint[], key: "count" | "errors" | "client_errors") {
61
+ return data.reduce((n, d) => n + d[key], 0)
62
+ }
63
+
64
+ // What the three stacked series mean for each record type. Rollups store
65
+ // them as count / client_errors / errors; the words differ per page.
66
+ export const VOLUME_LEGENDS = {
67
+ request: { ok: "1/2/3xx", warn: "4xx", error: "5xx" },
68
+ job: { ok: "processed", warn: "released", error: "failed" },
69
+ outcome: { ok: "ok", warn: null, error: "failed" },
70
+ exception: { ok: "handled", warn: null, error: "unhandled" },
71
+ cache: { ok: "hit", warn: "miss", error: "write" },
72
+ } as const
73
+
74
+ export type VolumeLegend = keyof typeof VOLUME_LEGENDS
75
+
76
+ // Volume panel: total as the headline, and a colour-keyed legend naming the
77
+ // ok / warning / error slices in the record type's own words.
78
+ export function VolumePanel({
79
+ data,
80
+ deploys,
81
+ label = "Requests",
82
+ seriesLabel,
83
+ legend = "request",
84
+ className,
85
+ }: {
86
+ data: SeriesPoint[]
87
+ deploys?: DeployMarker[]
88
+ label?: string
89
+ seriesLabel?: string
90
+ legend?: VolumeLegend
91
+ className?: string
92
+ }) {
93
+ const total = sum(data, "count")
94
+ const errors = sum(data, "errors")
95
+ const client = sum(data, "client_errors")
96
+ const words = VOLUME_LEGENDS[legend]
97
+ const items = [
98
+ {
99
+ key: "ok",
100
+ label: words.ok,
101
+ value: count(total - errors - client),
102
+ color: "var(--ok)",
103
+ },
104
+ words.warn && {
105
+ key: "warn",
106
+ label: words.warn,
107
+ value: count(client),
108
+ color: "var(--warning)",
109
+ },
110
+ {
111
+ key: "error",
112
+ label: words.error,
113
+ value: count(errors),
114
+ color: "var(--danger)",
115
+ },
116
+ ].filter((i): i is NonNullable<typeof i> => Boolean(i))
117
+ return (
118
+ <ChartPanel
119
+ label={label}
120
+ value={count(total)}
121
+ className={className}
122
+ legend={items}
123
+ >
124
+ <ThroughputChart data={data} deploys={deploys} label={seriesLabel} />
125
+ </ChartPanel>
126
+ )
127
+ }
128
+
129
+ // Duration panel: min — max range as the headline, avg and pNN legend.
130
+ export function DurationPanel({
131
+ data,
132
+ deploys,
133
+ percentile = "p95",
134
+ label = "Duration",
135
+ className,
136
+ }: {
137
+ data: SeriesPoint[]
138
+ deploys?: DeployMarker[]
139
+ percentile?: Percentile
140
+ label?: string
141
+ className?: string
142
+ }) {
143
+ const withData = data.filter((d) => d.count > 0)
144
+ const avgs = withData.flatMap((d) => (d.avg == null ? [] : [d.avg]))
145
+ const pcts = withData.flatMap((d) =>
146
+ d[percentile] == null ? [] : [d[percentile]],
147
+ )
148
+ const avg = avgs.length ? avgs.reduce((a, b) => a + b, 0) / avgs.length : 0
149
+ const pct = pcts.length ? Math.max(...pcts) : 0
150
+ const lo = avgs.length ? Math.min(...avgs) : 0
151
+ const hi = pcts.length ? Math.max(...pcts) : 0
152
+ return (
153
+ <ChartPanel
154
+ label={label}
155
+ value={withData.length ? `${ms(lo)} — ${ms(hi)}` : "—"}
156
+ className={className}
157
+ legend={[
158
+ { key: "avg", label: "avg", value: ms(avg), color: "var(--warning)" },
159
+ {
160
+ key: percentile,
161
+ label: percentile,
162
+ value: ms(pct),
163
+ color: "var(--primary)",
164
+ },
165
+ ]}
166
+ >
167
+ <LatencyChart data={data} deploys={deploys} percentile={percentile} />
168
+ </ChartPanel>
169
+ )
170
+ }
@@ -0,0 +1,28 @@
1
+ import { cn } from "@/lib/utils"
2
+
3
+ export function Sql({
4
+ children,
5
+ className,
6
+ }: {
7
+ children: string
8
+ className?: string
9
+ }) {
10
+ return (
11
+ <code
12
+ className={cn("block max-w-full truncate font-mono text-xs", className)}
13
+ title={children}
14
+ >
15
+ {children}
16
+ </code>
17
+ )
18
+ }
19
+
20
+ export function Mono({
21
+ children,
22
+ className,
23
+ }: {
24
+ children: React.ReactNode
25
+ className?: string
26
+ }) {
27
+ return <span className={cn("font-mono text-xs", className)}>{children}</span>
28
+ }
@@ -0,0 +1,132 @@
1
+ import { router, usePage } from "@inertiajs/react"
2
+ import { AlertOctagon, LayoutGrid, Plug, SearchIcon, Users } from "lucide-react"
3
+ import { useState } from "react"
4
+
5
+ import {
6
+ CommandDialog,
7
+ CommandEmpty,
8
+ CommandGroup,
9
+ CommandInput,
10
+ CommandItem,
11
+ CommandList,
12
+ CommandSeparator,
13
+ } from "@/components/ui/command"
14
+ import { envPages } from "@/lib/env-pages"
15
+ import {
16
+ applicationEnvironmentOverviewPath,
17
+ dashboardPath,
18
+ issuesPath,
19
+ settingsIntegrationsPath,
20
+ settingsMembersPath,
21
+ } from "@/routes"
22
+ import type { NavItem, SharedProps } from "@/types"
23
+
24
+ const staticNav = (embedded: boolean): NavItem[] => [
25
+ ...(embedded
26
+ ? []
27
+ : [{ title: "Dashboard", href: dashboardPath(), icon: LayoutGrid }]),
28
+ { title: "Issues", href: issuesPath(), icon: AlertOctagon },
29
+ ...(embedded
30
+ ? []
31
+ : [
32
+ { title: "Integrations", href: settingsIntegrationsPath(), icon: Plug },
33
+ { title: "Members", href: settingsMembersPath(), icon: Users },
34
+ ]),
35
+ ]
36
+
37
+ export function CommandPalette({
38
+ open,
39
+ onOpenChange,
40
+ }: {
41
+ open: boolean
42
+ onOpenChange: (open: boolean) => void
43
+ }) {
44
+ const [query, setQuery] = useState("")
45
+ const {
46
+ applications,
47
+ environment,
48
+ embedded,
49
+ window: currentWindow,
50
+ } = usePage<SharedProps>().props
51
+
52
+ const go = (href: string) => {
53
+ onOpenChange(false)
54
+ router.visit(href)
55
+ }
56
+
57
+ const envItems = environment
58
+ ? envPages(
59
+ environment.application_id,
60
+ environment.id,
61
+ currentWindow ?? "24h",
62
+ )
63
+ : []
64
+
65
+ return (
66
+ <CommandDialog open={open} onOpenChange={onOpenChange}>
67
+ <CommandInput
68
+ placeholder="Search pages, applications, issues…"
69
+ value={query}
70
+ onValueChange={setQuery}
71
+ />
72
+ <CommandList>
73
+ <CommandEmpty>No results found.</CommandEmpty>
74
+ <CommandGroup heading="Navigation">
75
+ {staticNav(!!embedded).map((item) => (
76
+ <CommandItem key={item.title} onSelect={() => go(item.href)}>
77
+ {item.icon && <item.icon />}
78
+ <span>{item.title}</span>
79
+ </CommandItem>
80
+ ))}
81
+ </CommandGroup>
82
+ {envItems.length > 0 && (
83
+ <CommandGroup
84
+ heading={`${environment!.application_name} · ${environment!.name}`}
85
+ >
86
+ {envItems.map((item) => (
87
+ <CommandItem key={item.title} onSelect={() => go(item.href)}>
88
+ {item.icon && <item.icon />}
89
+ <span>{item.title}</span>
90
+ </CommandItem>
91
+ ))}
92
+ </CommandGroup>
93
+ )}
94
+ {!embedded && (
95
+ <CommandGroup heading="Applications">
96
+ {applications.flatMap((app) =>
97
+ app.environments.map((env) => (
98
+ <CommandItem
99
+ key={`${app.id}-${env.id}`}
100
+ value={`${app.name} ${env.name}`}
101
+ onSelect={() =>
102
+ go(applicationEnvironmentOverviewPath(app.id, env.id))
103
+ }
104
+ >
105
+ <span>
106
+ {app.name} · {env.name}
107
+ </span>
108
+ </CommandItem>
109
+ )),
110
+ )}
111
+ </CommandGroup>
112
+ )}
113
+ {query.trim() && (
114
+ <>
115
+ <CommandSeparator />
116
+ <CommandGroup heading="Search">
117
+ <CommandItem
118
+ value={query}
119
+ onSelect={() =>
120
+ go(`${issuesPath()}?q=${encodeURIComponent(query)}`)
121
+ }
122
+ >
123
+ <SearchIcon />
124
+ <span>Search issues for &ldquo;{query}&rdquo;</span>
125
+ </CommandItem>
126
+ </CommandGroup>
127
+ </>
128
+ )}
129
+ </CommandList>
130
+ </CommandDialog>
131
+ )
132
+ }
@@ -0,0 +1,52 @@
1
+ import { CheckIcon, CopyIcon } from "lucide-react"
2
+ import { useState } from "react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { useClipboard } from "@/hooks/use-clipboard"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const COPIED_RESET_MS = 1500
9
+
10
+ // A code block with a copy button in its corner. Everything Railwatch asks a
11
+ // developer to paste somewhere else — a Gemfile line, a token, an MCP client
12
+ // config — goes through this, so "copy" behaves the same everywhere.
13
+ export function CopyBlock({
14
+ code,
15
+ className,
16
+ }: {
17
+ code: string
18
+ className?: string
19
+ }) {
20
+ const [, copy] = useClipboard()
21
+ const [justCopied, setJustCopied] = useState(false)
22
+
23
+ const handleCopy = () => {
24
+ void copy(code).then((ok) => {
25
+ if (!ok) return
26
+ setJustCopied(true)
27
+ window.setTimeout(() => setJustCopied(false), COPIED_RESET_MS)
28
+ })
29
+ }
30
+
31
+ return (
32
+ <div className={cn("relative", className)}>
33
+ <pre className="bg-muted overflow-x-auto rounded p-3 pr-10 font-mono text-xs">
34
+ {code}
35
+ </pre>
36
+ <Button
37
+ type="button"
38
+ aria-label={justCopied ? "Copied code" : "Copy code"}
39
+ variant="ghost"
40
+ size="icon"
41
+ className="absolute top-1.5 right-1.5 size-6"
42
+ onClick={handleCopy}
43
+ >
44
+ {justCopied ? (
45
+ <CheckIcon className="size-3.5 text-emerald-500" />
46
+ ) : (
47
+ <CopyIcon className="size-3.5 opacity-60" />
48
+ )}
49
+ </Button>
50
+ </div>
51
+ )
52
+ }
@@ -0,0 +1,49 @@
1
+ import { CheckIcon, CopyIcon } from "lucide-react"
2
+ import { useState } from "react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import { useClipboard } from "@/hooks/use-clipboard"
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const COPIED_RESET_MS = 1500
9
+
10
+ export function CopyId({
11
+ value,
12
+ label,
13
+ className,
14
+ }: {
15
+ value: string
16
+ label?: string
17
+ className?: string
18
+ }) {
19
+ const [, copy] = useClipboard()
20
+ const [justCopied, setJustCopied] = useState(false)
21
+
22
+ const handleCopy = () => {
23
+ void copy(value).then((ok) => {
24
+ if (!ok) return
25
+ setJustCopied(true)
26
+ window.setTimeout(() => setJustCopied(false), COPIED_RESET_MS)
27
+ })
28
+ }
29
+
30
+ return (
31
+ <Button
32
+ type="button"
33
+ variant="ghost"
34
+ size="sm"
35
+ className={cn(
36
+ "h-auto gap-1.5 px-1.5 py-0.5 font-mono text-xs",
37
+ className,
38
+ )}
39
+ onClick={handleCopy}
40
+ >
41
+ {label ?? value}
42
+ {justCopied ? (
43
+ <CheckIcon className="size-3 text-emerald-500" />
44
+ ) : (
45
+ <CopyIcon className="size-3 opacity-60" />
46
+ )}
47
+ </Button>
48
+ )
49
+ }
@@ -0,0 +1,39 @@
1
+ import { router } from "@inertiajs/react"
2
+ import { useState } from "react"
3
+
4
+ import { Button } from "@/components/ui/button"
5
+ import type { CursorMeta } from "@/types"
6
+
7
+ export function CursorLoadMore({
8
+ meta,
9
+ href,
10
+ only,
11
+ }: {
12
+ meta: CursorMeta
13
+ href: (cursor: string) => string
14
+ only: string[]
15
+ }) {
16
+ const [loading, setLoading] = useState(false)
17
+ if (!meta.has_more || !meta.next_cursor) return null
18
+
19
+ return (
20
+ <div className="flex justify-center pt-3">
21
+ <Button
22
+ type="button"
23
+ variant="outline"
24
+ disabled={loading}
25
+ onClick={() =>
26
+ router.visit(href(meta.next_cursor!), {
27
+ only,
28
+ preserveState: true,
29
+ preserveScroll: true,
30
+ onStart: () => setLoading(true),
31
+ onFinish: () => setLoading(false),
32
+ })
33
+ }
34
+ >
35
+ {loading ? "Loading…" : "Load " + meta.limit + " more"}
36
+ </Button>
37
+ </div>
38
+ )
39
+ }
@@ -0,0 +1,195 @@
1
+ import type { ReactNode } from "react"
2
+ import { useEffect, useRef } from "react"
3
+
4
+ import { useChartHover } from "@/components/railwatch/chart-hover"
5
+ import { EmptyState } from "@/components/railwatch/empty-state"
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from "@/components/ui/table"
14
+ import { useRowNav } from "@/hooks/use-row-nav"
15
+ import { cn } from "@/lib/utils"
16
+
17
+ const INTERACTIVE_TARGET =
18
+ 'a, button, input, select, textarea, [role="button"], [role="link"], [contenteditable="true"]'
19
+
20
+ function isInteractiveTarget(target: EventTarget | null) {
21
+ return target instanceof Element && target.closest(INTERACTIVE_TARGET)
22
+ }
23
+
24
+ export interface Column<T> {
25
+ key: string
26
+ header: ReactNode
27
+ cell: (row: T) => ReactNode
28
+ className?: string
29
+ align?: "left" | "right"
30
+ // Hide this column below the md breakpoint. Phones get the columns that
31
+ // identify the row and the one number that matters; the rest come back
32
+ // on wider screens. Every page marks its own secondary columns.
33
+ hideOnMobile?: boolean
34
+ // The column that identifies the row (route, SQL, job class). It takes
35
+ // whatever width the numeric columns leave over and truncates, so the
36
+ // table fits its container instead of forcing a sideways scroll.
37
+ grow?: boolean
38
+ }
39
+
40
+ export interface KeyboardNav<T> {
41
+ onOpen: (row: T, opts?: { newTab?: boolean }) => void
42
+ enabled?: boolean
43
+ // Fires whenever j/k/Esc change the highlighted row, so a page can drive
44
+ // other row-scoped shortcuts (e.g. issues' r/i/a) off the same state.
45
+ onHighlightChange?: (row: T | null) => void
46
+ }
47
+
48
+ export function DataTable<T>({
49
+ rows,
50
+ columns,
51
+ rowKey,
52
+ empty = "Nothing here yet.",
53
+ onRowClick,
54
+ keyboardNav,
55
+ hoverKey,
56
+ rowClassName,
57
+ className,
58
+ }: {
59
+ rows: T[]
60
+ columns: Column<T>[]
61
+ rowKey: (row: T) => string | number
62
+ empty?: ReactNode
63
+ onRowClick?: (row: T) => void
64
+ keyboardNav?: KeyboardNav<T>
65
+ // Per-row tint for a row whose own state matters (a crashed session), on
66
+ // top of the highlight/hover states below.
67
+ rowClassName?: (row: T) => string | undefined
68
+ // Links rows to a chart via ChartHoverProvider: hovering a row sets this
69
+ // key (e.g. the row's group_hash) as the shared hovered key, and rows
70
+ // whose key matches an externally-hovered chart point get `data-hovered`.
71
+ hoverKey?: (row: T) => string | undefined
72
+ className?: string
73
+ }) {
74
+ const { highlighted, activate } = useRowNav({
75
+ rows,
76
+ rowKey,
77
+ onOpen: keyboardNav?.onOpen ?? (() => undefined),
78
+ enabled: Boolean(keyboardNav) && keyboardNav?.enabled !== false,
79
+ })
80
+
81
+ const rowRefs = useRef(new Map<string | number, HTMLTableRowElement>())
82
+
83
+ useEffect(() => {
84
+ if (highlighted == null) return
85
+ rowRefs.current.get(highlighted)?.scrollIntoView({ block: "nearest" })
86
+ }, [highlighted])
87
+
88
+ useEffect(() => {
89
+ if (!keyboardNav?.onHighlightChange) return
90
+ const row = rows.find((r) => rowKey(r) === highlighted) ?? null
91
+ keyboardNav.onHighlightChange(row)
92
+ // eslint-disable-next-line react-hooks/exhaustive-deps
93
+ }, [highlighted])
94
+
95
+ const { hoveredKey, setHoveredKey } = useChartHover()
96
+ const colClass = (c: Column<T>) =>
97
+ cn(
98
+ c.align === "right" && "text-right",
99
+ c.hideOnMobile && "hidden md:table-cell",
100
+ c.grow && "w-full max-w-0 truncate",
101
+ c.className,
102
+ )
103
+
104
+ return (
105
+ <div
106
+ tabIndex={keyboardNav && keyboardNav.enabled !== false ? 0 : undefined}
107
+ onFocusCapture={activate}
108
+ onPointerDownCapture={activate}
109
+ className={cn(
110
+ "bg-card -mx-3 overflow-x-auto border-y md:mx-0 md:rounded-lg md:border",
111
+ className,
112
+ )}
113
+ >
114
+ <Table>
115
+ <TableHeader>
116
+ <TableRow className="hover:bg-transparent">
117
+ {columns.map((c) => (
118
+ <TableHead key={c.key} className={colClass(c)}>
119
+ {c.header}
120
+ </TableHead>
121
+ ))}
122
+ </TableRow>
123
+ </TableHeader>
124
+ <TableBody>
125
+ {rows.length === 0 && (
126
+ <TableRow>
127
+ <TableCell
128
+ colSpan={columns.length}
129
+ className="p-0 whitespace-normal"
130
+ >
131
+ {typeof empty === "string" ? (
132
+ <EmptyState title={empty} />
133
+ ) : (
134
+ <div className="text-muted-foreground min-h-24 text-center">
135
+ {empty}
136
+ </div>
137
+ )}
138
+ </TableCell>
139
+ </TableRow>
140
+ )}
141
+ {rows.map((row) => {
142
+ const key = rowKey(row)
143
+ const isHighlighted = Boolean(keyboardNav) && highlighted === key
144
+ const rowHoverKey = hoverKey?.(row)
145
+ const isHovered = rowHoverKey != null && hoveredKey === rowHoverKey
146
+ return (
147
+ <TableRow
148
+ key={key}
149
+ ref={(el) => {
150
+ if (el) rowRefs.current.set(key, el)
151
+ else rowRefs.current.delete(key)
152
+ }}
153
+ data-state={isHighlighted ? "highlighted" : undefined}
154
+ data-hovered={isHovered || undefined}
155
+ className={cn(
156
+ onRowClick && "cursor-pointer",
157
+ rowClassName?.(row),
158
+ isHighlighted &&
159
+ "bg-accent shadow-[inset_2px_0_0_0_var(--primary)]",
160
+ isHovered && !isHighlighted && "bg-accent/50",
161
+ )}
162
+ onClick={
163
+ onRowClick
164
+ ? (event) => {
165
+ if (!isInteractiveTarget(event.target)) onRowClick(row)
166
+ }
167
+ : undefined
168
+ }
169
+ onMouseEnter={
170
+ rowHoverKey ? () => setHoveredKey(rowHoverKey) : undefined
171
+ }
172
+ onMouseLeave={
173
+ rowHoverKey ? () => setHoveredKey(null) : undefined
174
+ }
175
+ >
176
+ {columns.map((c) => (
177
+ <TableCell
178
+ key={c.key}
179
+ className={cn(
180
+ "py-1.5 text-xs",
181
+ c.align === "right" && "font-mono tabular-nums",
182
+ colClass(c),
183
+ )}
184
+ >
185
+ {c.cell(row)}
186
+ </TableCell>
187
+ ))}
188
+ </TableRow>
189
+ )
190
+ })}
191
+ </TableBody>
192
+ </Table>
193
+ </div>
194
+ )
195
+ }