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,236 @@
1
+ import {
2
+ Activity,
3
+ AlertTriangle,
4
+ ArrowUpRight,
5
+ Bell,
6
+ BellRing,
7
+ Bug,
8
+ Building2,
9
+ Cable,
10
+ CalendarClock,
11
+ Database,
12
+ FileText,
13
+ Flame,
14
+ Gauge,
15
+ GitCommit,
16
+ HardDrive,
17
+ Layers,
18
+ LayoutTemplate,
19
+ Mail,
20
+ MonitorSmartphone,
21
+ Package,
22
+ PieChart,
23
+ Rocket,
24
+ Server,
25
+ Siren,
26
+ Sparkles,
27
+ Terminal,
28
+ Timer,
29
+ Users,
30
+ Workflow,
31
+ } from "lucide-react"
32
+
33
+ import * as R from "@/routes"
34
+ import type { NavItem } from "@/types"
35
+
36
+ export type EnvPageGroup = "activity" | "monitoring" | "settings"
37
+
38
+ export interface EnvPage extends NavItem {
39
+ group: EnvPageGroup
40
+ }
41
+
42
+ // Shared list of environment sub-pages, used by the sidebar EnvNav, the
43
+ // command palette, and the mobile page switcher so they never drift apart.
44
+ // Grouped the way Nightwatch groups its sidebar: Activity (what the app
45
+ // did), Monitoring (people, deploys, health), Settings (per-environment).
46
+ export function envPages(
47
+ applicationId: number,
48
+ environmentId: number,
49
+ window: string,
50
+ ): EnvPage[] {
51
+ const a = applicationId
52
+ const e = environmentId
53
+ const w = { window }
54
+ return [
55
+ {
56
+ title: "Overview",
57
+ href: R.applicationEnvironmentOverviewPath(a, e, w),
58
+ icon: Gauge,
59
+ group: "activity",
60
+ },
61
+ {
62
+ title: "Requests",
63
+ href: R.applicationEnvironmentRequestsPath(a, e, w),
64
+ icon: Activity,
65
+ group: "activity",
66
+ },
67
+ {
68
+ title: "Jobs",
69
+ href: R.applicationEnvironmentJobsPath(a, e, w),
70
+ icon: Workflow,
71
+ group: "activity",
72
+ },
73
+ {
74
+ title: "Scheduled tasks",
75
+ href: R.applicationEnvironmentScheduledTasksPath(a, e, w),
76
+ icon: CalendarClock,
77
+ group: "activity",
78
+ },
79
+ {
80
+ title: "Commands",
81
+ href: R.applicationEnvironmentCommandsPath(a, e, w),
82
+ icon: Terminal,
83
+ group: "activity",
84
+ },
85
+ {
86
+ title: "Exceptions",
87
+ href: R.applicationEnvironmentExceptionsPath(a, e, w),
88
+ icon: Bug,
89
+ group: "activity",
90
+ },
91
+ {
92
+ title: "Queries",
93
+ href: R.applicationEnvironmentQueriesPath(a, e, w),
94
+ icon: Database,
95
+ group: "activity",
96
+ },
97
+ {
98
+ title: "Spans",
99
+ href: R.applicationEnvironmentSpansPath(a, e, w),
100
+ icon: Timer,
101
+ group: "activity",
102
+ },
103
+ {
104
+ title: "Profiles",
105
+ href: R.applicationEnvironmentProfilesPath(a, e, w),
106
+ icon: Flame,
107
+ group: "activity",
108
+ },
109
+ {
110
+ title: "Transactions",
111
+ href: R.applicationEnvironmentTransactionsPath(a, e, w),
112
+ icon: GitCommit,
113
+ group: "activity",
114
+ },
115
+ {
116
+ title: "View renders",
117
+ href: R.applicationEnvironmentViewRendersPath(a, e, w),
118
+ icon: LayoutTemplate,
119
+ group: "activity",
120
+ },
121
+ {
122
+ title: "Cache",
123
+ href: R.applicationEnvironmentCacheEventsPath(a, e, w),
124
+ icon: Layers,
125
+ group: "activity",
126
+ },
127
+ {
128
+ title: "Mail",
129
+ href: R.applicationEnvironmentMailsPath(a, e, w),
130
+ icon: Mail,
131
+ group: "activity",
132
+ },
133
+ {
134
+ title: "Notifications",
135
+ href: R.applicationEnvironmentNotificationsPath(a, e, w),
136
+ icon: BellRing,
137
+ group: "activity",
138
+ },
139
+ {
140
+ title: "Broadcasts",
141
+ href: R.applicationEnvironmentBroadcastsPath(a, e, w),
142
+ icon: Cable,
143
+ group: "activity",
144
+ },
145
+ {
146
+ title: "Outgoing requests",
147
+ href: R.applicationEnvironmentOutgoingRequestsPath(a, e, w),
148
+ icon: ArrowUpRight,
149
+ group: "activity",
150
+ },
151
+ {
152
+ title: "LLM",
153
+ href: R.applicationEnvironmentLlmCallsPath(a, e, w),
154
+ icon: Sparkles,
155
+ group: "activity",
156
+ },
157
+ {
158
+ title: "Storage",
159
+ href: R.applicationEnvironmentStorageOpsPath(a, e, w),
160
+ icon: HardDrive,
161
+ group: "activity",
162
+ },
163
+ {
164
+ title: "Logs",
165
+ href: R.applicationEnvironmentLogsPath(a, e, w),
166
+ icon: FileText,
167
+ group: "activity",
168
+ },
169
+ {
170
+ title: "Deprecations",
171
+ href: R.applicationEnvironmentDeprecationsPath(a, e, w),
172
+ icon: AlertTriangle,
173
+ group: "activity",
174
+ },
175
+ {
176
+ title: "Visits",
177
+ href: R.applicationEnvironmentVisitsPath(a, e, w),
178
+ icon: MonitorSmartphone,
179
+ group: "monitoring",
180
+ },
181
+ {
182
+ title: "Users",
183
+ href: R.applicationEnvironmentPeoplePath(a, e, w),
184
+ icon: Users,
185
+ group: "monitoring",
186
+ },
187
+ {
188
+ title: "Tenants",
189
+ href: R.applicationEnvironmentTenantsPath(a, e, w),
190
+ icon: Building2,
191
+ group: "monitoring",
192
+ },
193
+ {
194
+ title: "Deploys",
195
+ href: R.applicationEnvironmentDeploysPath(a, e),
196
+ icon: Rocket,
197
+ group: "monitoring",
198
+ },
199
+ {
200
+ title: "Releases",
201
+ href: R.applicationEnvironmentReleasesPath(a, e, w),
202
+ icon: Package,
203
+ group: "monitoring",
204
+ },
205
+ {
206
+ title: "Processes",
207
+ href: R.applicationEnvironmentProcessesPath(a, e),
208
+ icon: Server,
209
+ group: "monitoring",
210
+ },
211
+ {
212
+ title: "Alerts",
213
+ href: R.applicationEnvironmentAlertsPath(a, e),
214
+ icon: Siren,
215
+ group: "monitoring",
216
+ },
217
+ {
218
+ title: "Thresholds",
219
+ href: R.applicationEnvironmentThresholdsPath(a, e),
220
+ icon: Bell,
221
+ group: "settings",
222
+ },
223
+ {
224
+ title: "Usage",
225
+ href: R.applicationEnvironmentUsagePath(a, e),
226
+ icon: PieChart,
227
+ group: "settings",
228
+ },
229
+ ]
230
+ }
231
+
232
+ export const ENV_PAGE_GROUPS: { key: EnvPageGroup; title: string }[] = [
233
+ { key: "activity", title: "Activity" },
234
+ { key: "monitoring", title: "Monitoring" },
235
+ { key: "settings", title: "Settings" },
236
+ ]
@@ -0,0 +1,55 @@
1
+ import * as R from "@/routes"
2
+
3
+ interface ExecutionPathOptions {
4
+ applicationId: number
5
+ environmentId: number
6
+ source: string | null | undefined
7
+ executionId: string | null | undefined
8
+ }
9
+
10
+ // Child telemetry uses `job` while the parent record itself is a
11
+ // `job_attempt`, so accept both wire names. Unknown server-side parent kinds
12
+ // use the generic execution surface; browser events intentionally have no
13
+ // server execution to open.
14
+ export function executionPath({
15
+ applicationId,
16
+ environmentId,
17
+ source,
18
+ executionId,
19
+ }: ExecutionPathOptions): string | null {
20
+ if (!executionId || source === "browser") return null
21
+
22
+ switch (source) {
23
+ case "request":
24
+ return R.applicationEnvironmentRequestPath(
25
+ applicationId,
26
+ environmentId,
27
+ executionId,
28
+ )
29
+ case "job":
30
+ case "job_attempt":
31
+ return R.applicationEnvironmentJobPath(
32
+ applicationId,
33
+ environmentId,
34
+ executionId,
35
+ )
36
+ case "scheduled_task":
37
+ return R.applicationEnvironmentScheduledTaskPath(
38
+ applicationId,
39
+ environmentId,
40
+ executionId,
41
+ )
42
+ case "command":
43
+ return R.applicationEnvironmentCommandPath(
44
+ applicationId,
45
+ environmentId,
46
+ executionId,
47
+ )
48
+ default:
49
+ return R.applicationEnvironmentExecutionPath(
50
+ applicationId,
51
+ environmentId,
52
+ executionId,
53
+ )
54
+ }
55
+ }
@@ -0,0 +1,50 @@
1
+ export interface ExplainWarning {
2
+ // 1-based line number within the plan text.
3
+ line: number
4
+ message: string
5
+ }
6
+
7
+ // The parts of a captured query plan that mean the database is doing more
8
+ // work than an index would let it. Each rule carries the plain-English
9
+ // reason, so the panel can say what's wrong rather than just colouring a
10
+ // line. Covers SQLite (EXPLAIN QUERY PLAN), Postgres and MySQL.
11
+ const RULES: { test: RegExp; skip?: RegExp; message: string }[] = [
12
+ {
13
+ test: /\bSCAN\b/i,
14
+ skip: /\bUSING\s+(?:COVERING\s+)?INDEX\b/i,
15
+ message: "Reads every row of the table — no index covers this filter.",
16
+ },
17
+ {
18
+ test: /\bSeq Scan\b/i,
19
+ message:
20
+ "Sequential scan — Postgres reads the whole table; an index on the filtered column avoids it.",
21
+ },
22
+ {
23
+ test: /\bUSING TEMP B-TREE\b/i,
24
+ message:
25
+ "Builds a temporary B-tree to sort — an index matching the ORDER BY removes it.",
26
+ },
27
+ {
28
+ test: /filesort/i,
29
+ message:
30
+ "Filesort — MySQL sorts the rows itself; an index matching the ORDER BY removes it.",
31
+ },
32
+ {
33
+ test: /\btype:\s*ALL\b/i,
34
+ message:
35
+ "Join type ALL — MySQL has no usable index and scans the whole table.",
36
+ },
37
+ ]
38
+
39
+ // Risky lines of `plan`, in order. One line can raise more than one warning
40
+ // (a full scan that also sorts), so callers should collect by line number
41
+ // rather than assume a single hit.
42
+ export function explainWarnings(plan: string): ExplainWarning[] {
43
+ return plan
44
+ .split("\n")
45
+ .flatMap((text, index) =>
46
+ RULES.filter((r) => r.test.test(text) && !r.skip?.test(text)).map(
47
+ (r) => ({ line: index + 1, message: r.message }),
48
+ ),
49
+ )
50
+ }
@@ -0,0 +1,25 @@
1
+ // Mirrors app/models/filters.rb: "key:value key2:value2 free text" tokens.
2
+ const TOKEN = /^([a-z_]+):(\S+)$/
3
+
4
+ export interface ParsedFilter {
5
+ text: string
6
+ fields: Record<string, string>
7
+ }
8
+
9
+ export function parseFilter(query: string): ParsedFilter {
10
+ const text: string[] = []
11
+ const fields: Record<string, string> = {}
12
+ for (const word of query.trim().split(/\s+/)) {
13
+ if (!word) continue
14
+ const m = TOKEN.exec(word)
15
+ if (m) fields[m[1]] = m[2]
16
+ else text.push(word)
17
+ }
18
+ return { text: text.join(" "), fields }
19
+ }
20
+
21
+ export function serializeFilter(parsed: ParsedFilter): string {
22
+ const tokens = Object.entries(parsed.fields).map(([k, v]) => `${k}:${v}`)
23
+ if (parsed.text) tokens.push(parsed.text)
24
+ return tokens.join(" ")
25
+ }
@@ -0,0 +1,155 @@
1
+ // Collapsed stacks ("outer;inner;leaf count\n", the format stackprof and
2
+ // vernier both fold to) turned into the tree and rectangles the flamegraph
3
+ // component draws. Pure data — no React, no DOM — so the layout maths is
4
+ // unit-testable on its own.
5
+
6
+ export interface FlameNode {
7
+ id: number
8
+ name: string
9
+ // Samples in this frame and everything it called.
10
+ value: number
11
+ // Samples where this frame was on top of the stack.
12
+ self: number
13
+ children: FlameNode[]
14
+ }
15
+
16
+ export interface FlameRect {
17
+ id: number
18
+ node: FlameNode
19
+ x: number
20
+ width: number
21
+ depth: number
22
+ // The value of the enclosing frame, for the tooltip's "% of parent".
23
+ parentValue: number
24
+ }
25
+
26
+ export interface FrameStat {
27
+ name: string
28
+ self: number
29
+ total: number
30
+ }
31
+
32
+ // Parses collapsed stacks into a tree rooted at a synthetic "all" frame,
33
+ // merging every stack that shares a prefix. Lines without a trailing sample
34
+ // count, with a non-numeric or non-positive count, or with no frames at all
35
+ // are skipped rather than throwing: a profile is a best-effort artefact and
36
+ // one bad line should not blank the page.
37
+ export function parseCollapsed(text: string): FlameNode {
38
+ let nextId = 0
39
+ const make = (name: string): FlameNode => ({
40
+ id: nextId++,
41
+ name,
42
+ value: 0,
43
+ self: 0,
44
+ children: [],
45
+ })
46
+
47
+ const root = make("all")
48
+ const index = new Map<string, FlameNode>()
49
+
50
+ for (const raw of text.split("\n")) {
51
+ const line = raw.trim()
52
+ const sep = line.lastIndexOf(" ")
53
+ if (sep <= 0) continue
54
+ const count = Number(line.slice(sep + 1))
55
+ if (!Number.isFinite(count) || count <= 0) continue
56
+ const frames = line
57
+ .slice(0, sep)
58
+ .split(";")
59
+ .filter((f) => f.length > 0)
60
+ if (frames.length === 0) continue
61
+
62
+ root.value += count
63
+ let current = root
64
+ let path = ""
65
+ for (const name of frames) {
66
+ path += ";" + name
67
+ let child = index.get(path)
68
+ if (!child) {
69
+ child = make(name)
70
+ current.children.push(child)
71
+ index.set(path, child)
72
+ }
73
+ child.value += count
74
+ current = child
75
+ }
76
+ current.self += count
77
+ }
78
+ return root
79
+ }
80
+
81
+ // x/width for every node, proportional to its share of `root`, with depth
82
+ // counted from the given root — so zooming is just calling this again with
83
+ // the clicked node.
84
+ export function layout(root: FlameNode, width: number): FlameRect[] {
85
+ const rects: FlameRect[] = []
86
+ const scale = root.value > 0 ? width / root.value : 0
87
+
88
+ const walk = (node: FlameNode, x: number, depth: number, parent: number) => {
89
+ rects.push({
90
+ id: node.id,
91
+ node,
92
+ x,
93
+ width: node.value * scale,
94
+ depth,
95
+ parentValue: parent,
96
+ })
97
+ let childX = x
98
+ for (const child of node.children) {
99
+ walk(child, childX, depth + 1, node.value)
100
+ childX += child.value * scale
101
+ }
102
+ }
103
+
104
+ walk(root, 0, 0, root.value)
105
+ return rects
106
+ }
107
+
108
+ // Same frame can appear under many callers; the hottest-frames table folds
109
+ // them by name the way stackprof's own report does. The synthetic root is
110
+ // left out — "all" is never an interesting frame.
111
+ function byName(root: FlameNode): Map<string, FrameStat> {
112
+ const stats = new Map<string, FrameStat>()
113
+ const walk = (node: FlameNode, isRoot: boolean) => {
114
+ if (!isRoot) {
115
+ const stat = stats.get(node.name) ?? {
116
+ name: node.name,
117
+ self: 0,
118
+ total: 0,
119
+ }
120
+ stat.self += node.self
121
+ stat.total += node.value
122
+ stats.set(node.name, stat)
123
+ }
124
+ for (const child of node.children) walk(child, false)
125
+ }
126
+ walk(root, true)
127
+ return stats
128
+ }
129
+
130
+ export function hottestSelf(root: FlameNode, n: number): FrameStat[] {
131
+ return [...byName(root).values()]
132
+ .sort((a, b) => b.self - a.self || b.total - a.total)
133
+ .slice(0, n)
134
+ }
135
+
136
+ export function hottestTotal(root: FlameNode, n: number): FrameStat[] {
137
+ return [...byName(root).values()]
138
+ .sort((a, b) => b.total - a.total || b.self - a.self)
139
+ .slice(0, n)
140
+ }
141
+
142
+ // Ids of every node whose frame name contains `query`, case-insensitively.
143
+ // A blank query matches nothing rather than everything, so clearing the box
144
+ // clears the highlight.
145
+ export function search(root: FlameNode, query: string): Set<number> {
146
+ const matches = new Set<number>()
147
+ const needle = query.trim().toLowerCase()
148
+ if (needle === "") return matches
149
+ const walk = (node: FlameNode) => {
150
+ if (node.name.toLowerCase().includes(needle)) matches.add(node.id)
151
+ for (const child of node.children) walk(child)
152
+ }
153
+ walk(root)
154
+ return matches
155
+ }
@@ -0,0 +1,71 @@
1
+ export function ms(value: number | null | undefined, digits = 1): string {
2
+ if (value === null || value === undefined) return "–"
3
+ if (value >= 1000) return `${(value / 1000).toFixed(2)}s`
4
+ if (value < 1 && value > 0) return `${(value * 1000).toFixed(0)}µs`
5
+ return `${value.toFixed(digits)}ms`
6
+ }
7
+
8
+ export function count(value: number | null | undefined): string {
9
+ if (value === null || value === undefined) return "–"
10
+ if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`
11
+ if (value >= 10_000) return `${(value / 1000).toFixed(1)}k`
12
+ return value.toLocaleString()
13
+ }
14
+
15
+ export function bytes(value: number | null | undefined): string {
16
+ if (!value) return "–"
17
+ if (value >= 1 << 30) return `${(value / (1 << 30)).toFixed(1)} GB`
18
+ if (value >= 1 << 20) return `${(value / (1 << 20)).toFixed(1)} MB`
19
+ if (value >= 1 << 10) return `${(value / (1 << 10)).toFixed(1)} KB`
20
+ return `${value} B`
21
+ }
22
+
23
+ // A single cheap model call costs a fraction of a cent, so the precision
24
+ // has to follow the magnitude: $0.0043 is the useful reading, $0.00 is not.
25
+ // null means unpriced, which is not the same as free -- callers that can
26
+ // say so in words should, rather than relying on the dash.
27
+ export function usd(value: number | null | undefined): string {
28
+ if (value === null || value === undefined) return "–"
29
+ if (value === 0) return "$0"
30
+ if (value < 0.01) return `$${value.toFixed(4)}`
31
+ if (value < 1000) return `$${value.toFixed(2)}`
32
+ return `$${Math.round(value).toLocaleString()}`
33
+ }
34
+
35
+ export function pct(numerator: number, denominator: number): string {
36
+ if (!denominator) return "0%"
37
+ return `${((numerator / denominator) * 100).toFixed(1)}%`
38
+ }
39
+
40
+ export function ago(iso: string | null | undefined): string {
41
+ if (!iso) return "never"
42
+ const diff = (Date.now() - new Date(iso).getTime()) / 1000
43
+ if (diff < 60) return `${Math.max(0, Math.floor(diff))}s ago`
44
+ if (diff < 3600) return `${Math.floor(diff / 60)}m ago`
45
+ if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`
46
+ return `${Math.floor(diff / 86400)}d ago`
47
+ }
48
+
49
+ export function when(iso: string | null | undefined): string {
50
+ if (!iso) return "–"
51
+ return new Date(iso).toLocaleString(undefined, {
52
+ month: "short",
53
+ day: "numeric",
54
+ hour: "2-digit",
55
+ minute: "2-digit",
56
+ second: "2-digit",
57
+ })
58
+ }
59
+
60
+ export function statusTone(
61
+ status: number | null | undefined,
62
+ outcome?: string | null,
63
+ ) {
64
+ if (outcome === "failed") return "destructive"
65
+ if (outcome === "processed") return "success"
66
+ if (!status) return "muted"
67
+ if (status >= 500) return "destructive"
68
+ if (status >= 400) return "warning"
69
+ if (status >= 300) return "muted"
70
+ return "success"
71
+ }
@@ -0,0 +1,18 @@
1
+ // Page shortcuts must yield to browser shortcuts, IME input, and controls.
2
+ export function ignorePageShortcut(event: KeyboardEvent) {
3
+ return (
4
+ event.defaultPrevented ||
5
+ event.isComposing ||
6
+ event.repeat ||
7
+ event.metaKey ||
8
+ event.ctrlKey ||
9
+ event.altKey ||
10
+ event.shiftKey ||
11
+ (event.target instanceof Element &&
12
+ Boolean(
13
+ event.target.closest(
14
+ 'input, textarea, select, button, a, [contenteditable]:not([contenteditable="false"]), [role="button"], [role="combobox"], [role="textbox"], [role="menu"], [role="listbox"], [role="dialog"]',
15
+ ),
16
+ ))
17
+ )
18
+ }