railwatch 0.9.3 → 1.0.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 (454) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +37 -33
  3. data/CHANGELOG.md +66 -0
  4. data/README.md +51 -60
  5. data/config/routes.rb +0 -59
  6. data/docs/ai-and-mcp.md +9 -8
  7. data/docs/configuration.md +57 -71
  8. data/docs/faq.md +17 -25
  9. data/docs/getting-started.md +105 -117
  10. data/docs/replacing-nightwatch.md +8 -7
  11. data/docs/replacing-sentry.md +12 -16
  12. data/docs/security.md +14 -20
  13. data/docs/self-hosting.md +24 -22
  14. data/docs/testing.md +6 -14
  15. data/docs/troubleshooting.md +12 -46
  16. data/lib/generators/railwatch/install/install_generator.rb +12 -240
  17. data/lib/generators/railwatch/install/templates/initializer.rb.tt +10 -40
  18. data/lib/generators/railwatch/install/templates/post-deploy +0 -7
  19. data/lib/railwatch/authentication.rb +6 -5
  20. data/lib/railwatch/buffer.rb +9 -0
  21. data/lib/railwatch/configuration.rb +21 -151
  22. data/lib/railwatch/engine.rb +9 -118
  23. data/lib/railwatch/patches.rb +0 -11
  24. data/lib/railwatch/reporter.rb +92 -21
  25. data/lib/railwatch/sampler.rb +10 -2
  26. data/lib/railwatch/subscribers/process_info.rb +1 -2
  27. data/lib/railwatch/version.rb +1 -1
  28. data/lib/railwatch.rb +8 -64
  29. data/lib/tasks/railwatch_tasks.rake +3 -325
  30. data/llms.txt +23 -25
  31. metadata +3 -453
  32. data/app/channels/railwatch/environment_channel.rb +0 -62
  33. data/app/controllers/concerns/railwatch/telemetry_identity.rb +0 -25
  34. data/app/controllers/railwatch/alerts_controller.rb +0 -48
  35. data/app/controllers/railwatch/anomaly_rules_controller.rb +0 -31
  36. data/app/controllers/railwatch/attachments_controller.rb +0 -22
  37. data/app/controllers/railwatch/broadcasts_controller.rb +0 -15
  38. data/app/controllers/railwatch/cache_events_controller.rb +0 -30
  39. data/app/controllers/railwatch/commands_controller.rb +0 -16
  40. data/app/controllers/railwatch/comments_controller.rb +0 -11
  41. data/app/controllers/railwatch/dashboard_controller.rb +0 -70
  42. data/app/controllers/railwatch/deploys_controller.rb +0 -34
  43. data/app/controllers/railwatch/deprecations_controller.rb +0 -54
  44. data/app/controllers/railwatch/environment_scoped.rb +0 -81
  45. data/app/controllers/railwatch/exceptions_controller.rb +0 -52
  46. data/app/controllers/railwatch/executions_controller.rb +0 -13
  47. data/app/controllers/railwatch/issues_controller.rb +0 -258
  48. data/app/controllers/railwatch/jobs_controller.rb +0 -63
  49. data/app/controllers/railwatch/llm_calls_controller.rb +0 -124
  50. data/app/controllers/railwatch/logs_controller.rb +0 -40
  51. data/app/controllers/railwatch/mails_controller.rb +0 -19
  52. data/app/controllers/railwatch/notifications_controller.rb +0 -11
  53. data/app/controllers/railwatch/outgoing_requests_controller.rb +0 -19
  54. data/app/controllers/railwatch/overview_controller.rb +0 -38
  55. data/app/controllers/railwatch/people_controller.rb +0 -25
  56. data/app/controllers/railwatch/processes_controller.rb +0 -33
  57. data/app/controllers/railwatch/profiles_controller.rb +0 -65
  58. data/app/controllers/railwatch/queries_controller.rb +0 -59
  59. data/app/controllers/railwatch/releases_controller.rb +0 -75
  60. data/app/controllers/railwatch/requests_controller.rb +0 -37
  61. data/app/controllers/railwatch/saved_views_controller.rb +0 -53
  62. data/app/controllers/railwatch/scheduled_tasks_controller.rb +0 -45
  63. data/app/controllers/railwatch/spans_controller.rb +0 -47
  64. data/app/controllers/railwatch/storage_ops_controller.rb +0 -15
  65. data/app/controllers/railwatch/tenants_controller.rb +0 -44
  66. data/app/controllers/railwatch/thresholds_controller.rb +0 -47
  67. data/app/controllers/railwatch/traces_controller.rb +0 -72
  68. data/app/controllers/railwatch/transactions_controller.rb +0 -21
  69. data/app/controllers/railwatch/view_renders_controller.rb +0 -28
  70. data/app/controllers/railwatch/visits_controller.rb +0 -53
  71. data/app/frontend/components/app-content.tsx +0 -26
  72. data/app/frontend/components/app-logo-icon.tsx +0 -23
  73. data/app/frontend/components/app-logo.tsx +0 -14
  74. data/app/frontend/components/app-shell.tsx +0 -38
  75. data/app/frontend/components/app-sidebar-header.tsx +0 -54
  76. data/app/frontend/components/app-sidebar.tsx +0 -94
  77. data/app/frontend/components/app-wordmark.tsx +0 -29
  78. data/app/frontend/components/breadcrumbs.tsx +0 -46
  79. data/app/frontend/components/icon.tsx +0 -16
  80. data/app/frontend/components/input-error.tsx +0 -18
  81. data/app/frontend/components/nav-footer.tsx +0 -46
  82. data/app/frontend/components/nav-main.tsx +0 -41
  83. data/app/frontend/components/nav-user.tsx +0 -50
  84. data/app/frontend/components/railwatch/chart-hover.tsx +0 -34
  85. data/app/frontend/components/railwatch/chart-panel.tsx +0 -170
  86. data/app/frontend/components/railwatch/code.tsx +0 -28
  87. data/app/frontend/components/railwatch/command-palette.tsx +0 -132
  88. data/app/frontend/components/railwatch/copy-block.tsx +0 -52
  89. data/app/frontend/components/railwatch/copy-id.tsx +0 -49
  90. data/app/frontend/components/railwatch/cursor-load-more.tsx +0 -39
  91. data/app/frontend/components/railwatch/data-table.tsx +0 -195
  92. data/app/frontend/components/railwatch/digit-roll.tsx +0 -312
  93. data/app/frontend/components/railwatch/empty-state.tsx +0 -159
  94. data/app/frontend/components/railwatch/env-nav.tsx +0 -130
  95. data/app/frontend/components/railwatch/env-switcher.tsx +0 -145
  96. data/app/frontend/components/railwatch/filter-bar.tsx +0 -153
  97. data/app/frontend/components/railwatch/flamegraph.tsx +0 -324
  98. data/app/frontend/components/railwatch/frames.tsx +0 -295
  99. data/app/frontend/components/railwatch/json-viewer.tsx +0 -159
  100. data/app/frontend/components/railwatch/live-dot.tsx +0 -78
  101. data/app/frontend/components/railwatch/live-toggle.tsx +0 -76
  102. data/app/frontend/components/railwatch/metric-chart.tsx +0 -112
  103. data/app/frontend/components/railwatch/mobile-page-switcher.tsx +0 -89
  104. data/app/frontend/components/railwatch/onboarding.tsx +0 -91
  105. data/app/frontend/components/railwatch/origin-identity.tsx +0 -84
  106. data/app/frontend/components/railwatch/page-header.tsx +0 -42
  107. data/app/frontend/components/railwatch/percentile-picker.tsx +0 -18
  108. data/app/frontend/components/railwatch/relative-time.tsx +0 -18
  109. data/app/frontend/components/railwatch/saved-views.tsx +0 -232
  110. data/app/frontend/components/railwatch/segmented.tsx +0 -64
  111. data/app/frontend/components/railwatch/series-chart.tsx +0 -400
  112. data/app/frontend/components/railwatch/shortcuts-dialog.tsx +0 -89
  113. data/app/frontend/components/railwatch/signal-toast.tsx +0 -121
  114. data/app/frontend/components/railwatch/sort-header.tsx +0 -37
  115. data/app/frontend/components/railwatch/source-link.tsx +0 -57
  116. data/app/frontend/components/railwatch/sparkline-cell.tsx +0 -14
  117. data/app/frontend/components/railwatch/sql-block.tsx +0 -116
  118. data/app/frontend/components/railwatch/stat.tsx +0 -185
  119. data/app/frontend/components/railwatch/status-badge.tsx +0 -79
  120. data/app/frontend/components/railwatch/step-picker.tsx +0 -57
  121. data/app/frontend/components/railwatch/timeline.tsx +0 -405
  122. data/app/frontend/components/railwatch/utilisation-bar.tsx +0 -44
  123. data/app/frontend/components/railwatch/window-picker.tsx +0 -147
  124. data/app/frontend/components/ui/avatar.tsx +0 -51
  125. data/app/frontend/components/ui/badge.tsx +0 -46
  126. data/app/frontend/components/ui/breadcrumb.tsx +0 -109
  127. data/app/frontend/components/ui/button.tsx +0 -60
  128. data/app/frontend/components/ui/card.tsx +0 -92
  129. data/app/frontend/components/ui/chart.tsx +0 -374
  130. data/app/frontend/components/ui/checkbox.tsx +0 -32
  131. data/app/frontend/components/ui/collapsible.tsx +0 -31
  132. data/app/frontend/components/ui/command.tsx +0 -182
  133. data/app/frontend/components/ui/dialog.tsx +0 -158
  134. data/app/frontend/components/ui/dropdown-menu.tsx +0 -255
  135. data/app/frontend/components/ui/hover-card.tsx +0 -42
  136. data/app/frontend/components/ui/input.tsx +0 -21
  137. data/app/frontend/components/ui/label.tsx +0 -24
  138. data/app/frontend/components/ui/popover.tsx +0 -87
  139. data/app/frontend/components/ui/select.tsx +0 -185
  140. data/app/frontend/components/ui/separator.tsx +0 -28
  141. data/app/frontend/components/ui/sheet.tsx +0 -137
  142. data/app/frontend/components/ui/sidebar.tsx +0 -726
  143. data/app/frontend/components/ui/skeleton.tsx +0 -13
  144. data/app/frontend/components/ui/sonner.tsx +0 -41
  145. data/app/frontend/components/ui/table.tsx +0 -114
  146. data/app/frontend/components/ui/tabs.tsx +0 -91
  147. data/app/frontend/components/ui/textarea.tsx +0 -18
  148. data/app/frontend/components/ui/tooltip.tsx +0 -61
  149. data/app/frontend/components/user-info.tsx +0 -32
  150. data/app/frontend/components/user-menu-content.tsx +0 -94
  151. data/app/frontend/entrypoints/application.css +0 -244
  152. data/app/frontend/entrypoints/inertia.tsx +0 -92
  153. data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
  154. data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
  155. data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
  156. data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
  157. data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
  158. data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
  159. data/app/frontend/hooks/use-appearance.tsx +0 -67
  160. data/app/frontend/hooks/use-clipboard.ts +0 -32
  161. data/app/frontend/hooks/use-flash.tsx +0 -23
  162. data/app/frontend/hooks/use-hotkeys.ts +0 -63
  163. data/app/frontend/hooks/use-initials.tsx +0 -15
  164. data/app/frontend/hooks/use-live.ts +0 -205
  165. data/app/frontend/hooks/use-mobile-navigation.ts +0 -8
  166. data/app/frontend/hooks/use-mobile.ts +0 -21
  167. data/app/frontend/hooks/use-percentile.ts +0 -24
  168. data/app/frontend/hooks/use-reduced-motion.ts +0 -25
  169. data/app/frontend/hooks/use-row-nav.ts +0 -106
  170. data/app/frontend/hooks/use-step.ts +0 -27
  171. data/app/frontend/hooks/use-ticker.ts +0 -12
  172. data/app/frontend/hooks/use-window.ts +0 -58
  173. data/app/frontend/layouts/app/app-sidebar-layout.tsx +0 -107
  174. data/app/frontend/layouts/app-layout.tsx +0 -21
  175. data/app/frontend/layouts/env-layout.tsx +0 -53
  176. data/app/frontend/layouts/persistent-layout.tsx +0 -18
  177. data/app/frontend/lib/cable.ts +0 -10
  178. data/app/frontend/lib/env-pages.ts +0 -236
  179. data/app/frontend/lib/execution-path.ts +0 -55
  180. data/app/frontend/lib/explain.ts +0 -50
  181. data/app/frontend/lib/filter.ts +0 -25
  182. data/app/frontend/lib/flamegraph.ts +0 -155
  183. data/app/frontend/lib/format.ts +0 -71
  184. data/app/frontend/lib/keyboard.ts +0 -18
  185. data/app/frontend/lib/mount.ts +0 -23
  186. data/app/frontend/lib/source-link.ts +0 -217
  187. data/app/frontend/lib/utils.ts +0 -6
  188. data/app/frontend/pages/alerts/index.tsx +0 -171
  189. data/app/frontend/pages/broadcasts/index.tsx +0 -171
  190. data/app/frontend/pages/cache_events/index.tsx +0 -127
  191. data/app/frontend/pages/commands/index.tsx +0 -143
  192. data/app/frontend/pages/deploys/index.tsx +0 -94
  193. data/app/frontend/pages/deploys/show.tsx +0 -309
  194. data/app/frontend/pages/deprecations/index.tsx +0 -176
  195. data/app/frontend/pages/exceptions/index.tsx +0 -271
  196. data/app/frontend/pages/executions/show.tsx +0 -821
  197. data/app/frontend/pages/issues/index.tsx +0 -399
  198. data/app/frontend/pages/issues/show.tsx +0 -1367
  199. data/app/frontend/pages/jobs/index.tsx +0 -381
  200. data/app/frontend/pages/jobs/klass.tsx +0 -215
  201. data/app/frontend/pages/llm_calls/index.tsx +0 -664
  202. data/app/frontend/pages/logs/index.tsx +0 -303
  203. data/app/frontend/pages/mails/index.tsx +0 -218
  204. data/app/frontend/pages/notifications/index.tsx +0 -161
  205. data/app/frontend/pages/outgoing_requests/index.tsx +0 -309
  206. data/app/frontend/pages/overview/show.tsx +0 -333
  207. data/app/frontend/pages/people/index.tsx +0 -102
  208. data/app/frontend/pages/people/show.tsx +0 -149
  209. data/app/frontend/pages/processes/index.tsx +0 -335
  210. data/app/frontend/pages/profiles/index.tsx +0 -110
  211. data/app/frontend/pages/profiles/show.tsx +0 -105
  212. data/app/frontend/pages/queries/index.tsx +0 -492
  213. data/app/frontend/pages/queries/show.tsx +0 -276
  214. data/app/frontend/pages/releases/index.tsx +0 -190
  215. data/app/frontend/pages/releases/release-health.tsx +0 -68
  216. data/app/frontend/pages/releases/release-path.ts +0 -25
  217. data/app/frontend/pages/releases/show.tsx +0 -262
  218. data/app/frontend/pages/requests/index.tsx +0 -235
  219. data/app/frontend/pages/requests/route.tsx +0 -159
  220. data/app/frontend/pages/scheduled_tasks/index.tsx +0 -220
  221. data/app/frontend/pages/spans/index.tsx +0 -229
  222. data/app/frontend/pages/spans/show.tsx +0 -220
  223. data/app/frontend/pages/storage_ops/index.tsx +0 -150
  224. data/app/frontend/pages/tenants/index.tsx +0 -236
  225. data/app/frontend/pages/tenants/show.tsx +0 -477
  226. data/app/frontend/pages/tenants/tenant-path.ts +0 -26
  227. data/app/frontend/pages/thresholds/index.tsx +0 -414
  228. data/app/frontend/pages/traces/show.tsx +0 -208
  229. data/app/frontend/pages/transactions/index.tsx +0 -220
  230. data/app/frontend/pages/view_renders/index.tsx +0 -241
  231. data/app/frontend/pages/visits/index.tsx +0 -321
  232. data/app/frontend/public/icon.png +0 -0
  233. data/app/frontend/public/icon.svg +0 -5
  234. data/app/frontend/routes/index.d.ts +0 -2136
  235. data/app/frontend/routes/index.js +0 -2112
  236. data/app/frontend/types/globals.d.ts +0 -9
  237. data/app/frontend/types/index.ts +0 -288
  238. data/app/frontend/types/vite-env.d.ts +0 -9
  239. data/app/helpers/railwatch/assets_helper.rb +0 -52
  240. data/app/jobs/railwatch/anomaly_scan_job.rb +0 -11
  241. data/app/jobs/railwatch/application_job.rb +0 -7
  242. data/app/jobs/railwatch/auto_resolve_issues_job.rb +0 -19
  243. data/app/jobs/railwatch/check_scheduled_tasks_job.rb +0 -113
  244. data/app/jobs/railwatch/detect_anomalies_job.rb +0 -171
  245. data/app/jobs/railwatch/detect_performance_issues_job.rb +0 -95
  246. data/app/jobs/railwatch/group_exceptions_job.rb +0 -91
  247. data/app/jobs/railwatch/optimize_telemetry_job.rb +0 -23
  248. data/app/jobs/railwatch/performance_scan_job.rb +0 -11
  249. data/app/jobs/railwatch/prune_telemetry_job.rb +0 -129
  250. data/app/jobs/railwatch/release_health_rollup_job.rb +0 -64
  251. data/app/jobs/railwatch/rollup_catchup_job.rb +0 -21
  252. data/app/jobs/railwatch/rollup_job.rb +0 -132
  253. data/app/jobs/railwatch/scheduled_task_scan_job.rb +0 -11
  254. data/app/models/concerns/railwatch/detection_snapshotting.rb +0 -20
  255. data/app/models/railwatch/alert.rb +0 -229
  256. data/app/models/railwatch/alert_rule.rb +0 -121
  257. data/app/models/railwatch/anomaly_rule.rb +0 -51
  258. data/app/models/railwatch/application.rb +0 -44
  259. data/app/models/railwatch/application_record.rb +0 -33
  260. data/app/models/railwatch/comment.rb +0 -36
  261. data/app/models/railwatch/deploy.rb +0 -67
  262. data/app/models/railwatch/environment.rb +0 -68
  263. data/app/models/railwatch/execution_presenter.rb +0 -185
  264. data/app/models/railwatch/filter_query.rb +0 -184
  265. data/app/models/railwatch/followup_receipt.rb +0 -27
  266. data/app/models/railwatch/ingest/batch.rb +0 -355
  267. data/app/models/railwatch/ingest/mapper.rb +0 -585
  268. data/app/models/railwatch/ingest/payload.rb +0 -96
  269. data/app/models/railwatch/ingest/rollup_absorber.rb +0 -170
  270. data/app/models/railwatch/ingest/writer.rb +0 -137
  271. data/app/models/railwatch/issue.rb +0 -277
  272. data/app/models/railwatch/issue_activity.rb +0 -23
  273. data/app/models/railwatch/issue_detection_presenter.rb +0 -309
  274. data/app/models/railwatch/issue_detection_snapshot.rb +0 -77
  275. data/app/models/railwatch/maintenance_task.rb +0 -53
  276. data/app/models/railwatch/saved_view.rb +0 -63
  277. data/app/models/railwatch/telemetry/aggregations.rb +0 -251
  278. data/app/models/railwatch/telemetry/attachment.rb +0 -31
  279. data/app/models/railwatch/telemetry/bounded_gzip.rb +0 -101
  280. data/app/models/railwatch/telemetry/broadcast.rb +0 -13
  281. data/app/models/railwatch/telemetry/cache_event.rb +0 -16
  282. data/app/models/railwatch/telemetry/child.rb +0 -53
  283. data/app/models/railwatch/telemetry/cursor_page.rb +0 -99
  284. data/app/models/railwatch/telemetry/deprecation.rb +0 -13
  285. data/app/models/railwatch/telemetry/enqueued_job.rb +0 -13
  286. data/app/models/railwatch/telemetry/exception.rb +0 -28
  287. data/app/models/railwatch/telemetry/execution.rb +0 -152
  288. data/app/models/railwatch/telemetry/export_delivery.rb +0 -32
  289. data/app/models/railwatch/telemetry/export_destination.rb +0 -66
  290. data/app/models/railwatch/telemetry/health_sample.rb +0 -72
  291. data/app/models/railwatch/telemetry/ingest_batch.rb +0 -31
  292. data/app/models/railwatch/telemetry/llm_call.rb +0 -37
  293. data/app/models/railwatch/telemetry/log.rb +0 -91
  294. data/app/models/railwatch/telemetry/mail.rb +0 -13
  295. data/app/models/railwatch/telemetry/n_plus_one.rb +0 -92
  296. data/app/models/railwatch/telemetry/notification.rb +0 -13
  297. data/app/models/railwatch/telemetry/outgoing_request.rb +0 -13
  298. data/app/models/railwatch/telemetry/person.rb +0 -50
  299. data/app/models/railwatch/telemetry/process.rb +0 -10
  300. data/app/models/railwatch/telemetry/profile.rb +0 -37
  301. data/app/models/railwatch/telemetry/query.rb +0 -57
  302. data/app/models/railwatch/telemetry/query_shape.rb +0 -45
  303. data/app/models/railwatch/telemetry/release_health.rb +0 -69
  304. data/app/models/railwatch/telemetry/rollup.rb +0 -157
  305. data/app/models/railwatch/telemetry/session.rb +0 -24
  306. data/app/models/railwatch/telemetry/span.rb +0 -19
  307. data/app/models/railwatch/telemetry/storage_op.rb +0 -13
  308. data/app/models/railwatch/telemetry/tenant.rb +0 -240
  309. data/app/models/railwatch/telemetry/transaction.rb +0 -13
  310. data/app/models/railwatch/telemetry/view_render.rb +0 -13
  311. data/app/models/railwatch/telemetry/visit.rb +0 -24
  312. data/app/models/railwatch/telemetry_record.rb +0 -107
  313. data/app/models/railwatch/threshold.rb +0 -66
  314. data/app/models/railwatch/user.rb +0 -47
  315. data/app/models/railwatch/viewer.rb +0 -13
  316. data/app/models/railwatch/window.rb +0 -66
  317. data/app/views/layouts/railwatch/dashboard.html.erb +0 -25
  318. data/db/railwatch_migrate/20260916000000_create_railwatch_tables.rb +0 -151
  319. data/db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb +0 -18
  320. data/db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb +0 -20
  321. data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +0 -71
  322. data/db/railwatch_telemetry_migrate/20260903000000_enable_incremental_vacuum.rb +0 -62
  323. data/db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb +0 -481
  324. data/db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb +0 -14
  325. data/db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb +0 -9
  326. data/db/railwatch_telemetry_migrate/20260903000004_add_role_and_channel.rb +0 -10
  327. data/db/railwatch_telemetry_migrate/20260903000005_add_locals_to_exceptions.rb +0 -9
  328. data/db/railwatch_telemetry_migrate/20260903000006_add_spans_health_vitals_and_fts.rb +0 -82
  329. data/db/railwatch_telemetry_migrate/20260903000007_rename_span_attributes_to_payload.rb +0 -10
  330. data/db/railwatch_telemetry_migrate/20260903000008_add_profiles_and_attachments.rb +0 -60
  331. data/db/railwatch_telemetry_migrate/20260903000009_add_truncated_to_attachments.rb +0 -9
  332. data/db/railwatch_telemetry_migrate/20260903000010_create_sessions_and_release_health.rb +0 -51
  333. data/db/railwatch_telemetry_migrate/20260903000011_add_fingerprint_to_exceptions.rb +0 -11
  334. data/db/railwatch_telemetry_migrate/20260904000012_add_failed_to_broadcasts.rb +0 -10
  335. data/db/railwatch_telemetry_migrate/20260904010000_add_filter_cursor_indexes.rb +0 -37
  336. data/db/railwatch_telemetry_migrate/20260904020000_add_n_plus_ones_execution_id_index.rb +0 -11
  337. data/db/railwatch_telemetry_migrate/20260904120000_create_query_shapes.rb +0 -15
  338. data/db/railwatch_telemetry_migrate/20260906120000_add_backpressure_factor_to_ingest_batches.rb +0 -9
  339. data/db/railwatch_telemetry_migrate/20260907000000_rename_lantern_version_on_processes.rb +0 -10
  340. data/db/railwatch_telemetry_migrate/20260913000000_rename_nightrail_version_on_processes.rb +0 -9
  341. data/db/railwatch_telemetry_migrate/20260914000000_drop_orphan_durable_ingest_tables.rb +0 -18
  342. data/db/railwatch_telemetry_migrate/20260914010000_drop_orphan_durable_ingest_columns.rb +0 -25
  343. data/db/railwatch_telemetry_migrate/20260915000000_create_llm_calls.rb +0 -55
  344. data/db/railwatch_telemetry_migrate/20260915120000_add_detail_to_llm_calls.rb +0 -21
  345. data/db/railwatch_telemetry_migrate/20260915200000_add_explained_index_to_queries.rb +0 -16
  346. data/db/railwatch_telemetry_migrate/20260915210000_add_slowest_index_to_queries.rb +0 -18
  347. data/db/railwatch_telemetry_migrate/20260917010000_add_batch_ledger_to_ingest_batches.rb +0 -16
  348. data/db/railwatch_telemetry_migrate/20260919000100_create_export_queue.rb +0 -146
  349. data/db/railwatch_telemetry_migrate/20260922000000_add_covering_indexes_for_dashboard_aggregates.rb +0 -34
  350. data/db/railwatch_telemetry_migrate/20260923000000_add_people_index.rb +0 -15
  351. data/db/railwatch_telemetry_migrate/20260925000000_add_tenant_summary_index.rb +0 -17
  352. data/db/railwatch_telemetry_migrate/20261008000000_add_in_transaction_to_outgoing_requests.rb +0 -10
  353. data/docs/embedded.md +0 -503
  354. data/lib/puma/plugin/railwatch.rb +0 -215
  355. data/lib/railwatch/dashboard_assets.rb +0 -45
  356. data/lib/railwatch/embedded.rb +0 -50
  357. data/lib/railwatch/export/client.rb +0 -124
  358. data/lib/railwatch/export/lease.rb +0 -56
  359. data/lib/railwatch/export/outbox.rb +0 -361
  360. data/lib/railwatch/export/policy.rb +0 -79
  361. data/lib/railwatch/export/sender.rb +0 -130
  362. data/lib/railwatch/ingest_request_body_limit.rb +0 -10
  363. data/lib/railwatch/maintenance.rb +0 -194
  364. data/lib/railwatch/patches/migration_busy_timeout.rb +0 -73
  365. data/lib/railwatch/transport/local.rb +0 -78
  366. data/lib/railwatch/transport/socket.rb +0 -183
  367. data/lib/railwatch/writer.rb +0 -370
  368. data/public/railwatch/assets/CommitMono-Bold-D6h61ieg.woff2 +0 -0
  369. data/public/railwatch/assets/CommitMono-Regular-zr8w7Obm.woff2 +0 -0
  370. data/public/railwatch/assets/Roboto-Black-auA4GeOK.woff2 +0 -0
  371. data/public/railwatch/assets/Roboto-Bold-CJLnO8j1.woff2 +0 -0
  372. data/public/railwatch/assets/Roboto-Medium-Cm2bwKpj.woff2 +0 -0
  373. data/public/railwatch/assets/Roboto-Regular-Chaq1-PV.woff2 +0 -0
  374. data/public/railwatch/assets/app-layout-by7G3sa1.js +0 -41
  375. data/public/railwatch/assets/application-Cc5v_Vk9.css +0 -1
  376. data/public/railwatch/assets/arrow-up-DCZ7emux.js +0 -1
  377. data/public/railwatch/assets/braces-CykT5_5u.js +0 -1
  378. data/public/railwatch/assets/card-C-PPSA0g.js +0 -1
  379. data/public/railwatch/assets/chart-Cov79YC5.js +0 -39
  380. data/public/railwatch/assets/chart-hover-DdK4E-2h.js +0 -1
  381. data/public/railwatch/assets/chart-panel-BPaYRXJB.js +0 -1
  382. data/public/railwatch/assets/code-BYjC4YZ_.js +0 -1
  383. data/public/railwatch/assets/copy-id-LPfoj9cC.js +0 -1
  384. data/public/railwatch/assets/cursor-load-more-DSdhgpGl.js +0 -1
  385. data/public/railwatch/assets/data-table-CQ_YRWQs.js +0 -1
  386. data/public/railwatch/assets/env-layout-CdMWbUkm.js +0 -1
  387. data/public/railwatch/assets/execution-path-7MYt-Qbn.js +0 -1
  388. data/public/railwatch/assets/filter-bar-CrzpQ_vD.js +0 -1
  389. data/public/railwatch/assets/flamegraph-BIFEdTAg.js +0 -2
  390. data/public/railwatch/assets/format-B9SDkrWj.js +0 -1
  391. data/public/railwatch/assets/frames-C5t-x4f0.js +0 -1
  392. data/public/railwatch/assets/index--NQ7us-L.js +0 -1
  393. data/public/railwatch/assets/index-2kgYe_99.js +0 -1
  394. data/public/railwatch/assets/index-4YEPHxtO.js +0 -1
  395. data/public/railwatch/assets/index-AOCC1GDJ.js +0 -1
  396. data/public/railwatch/assets/index-BEo-u0eO.js +0 -1
  397. data/public/railwatch/assets/index-BJ1V3w2N.js +0 -1
  398. data/public/railwatch/assets/index-BPoHsBKb.js +0 -1
  399. data/public/railwatch/assets/index-BZzyGUM3.js +0 -1
  400. data/public/railwatch/assets/index-BgVq7yKL.js +0 -1
  401. data/public/railwatch/assets/index-BqFGEZvW.js +0 -1
  402. data/public/railwatch/assets/index-ByuHJ1CH.js +0 -1
  403. data/public/railwatch/assets/index-C2j50AL2.js +0 -1
  404. data/public/railwatch/assets/index-CLEdTkvW.js +0 -1
  405. data/public/railwatch/assets/index-CWgq8qBu.js +0 -1
  406. data/public/railwatch/assets/index-CXQ7_oVj.js +0 -2
  407. data/public/railwatch/assets/index-CmyYrjS2.js +0 -1
  408. data/public/railwatch/assets/index-Cn9_HjDo.js +0 -1
  409. data/public/railwatch/assets/index-CsMAm9UK.js +0 -1
  410. data/public/railwatch/assets/index-D0Kk_oGY.js +0 -1
  411. data/public/railwatch/assets/index-DC9ZptJh.js +0 -1
  412. data/public/railwatch/assets/index-DNzg7jmV.js +0 -1
  413. data/public/railwatch/assets/index-DQ8uJO5f.js +0 -1
  414. data/public/railwatch/assets/index-Deq14BxB.js +0 -1
  415. data/public/railwatch/assets/index-IaV3Blzp.js +0 -1
  416. data/public/railwatch/assets/index-TYNWcJQs.js +0 -1
  417. data/public/railwatch/assets/index-WonH2fcZ.js +0 -1
  418. data/public/railwatch/assets/index-WxWUaH_d.js +0 -1
  419. data/public/railwatch/assets/index-mUNrl4ky.js +0 -1
  420. data/public/railwatch/assets/inertia-DfNRARuA.js +0 -97
  421. data/public/railwatch/assets/json-viewer-DMsF5glp.js +0 -1
  422. data/public/railwatch/assets/klass-CmHYk5rz.js +0 -1
  423. data/public/railwatch/assets/origin-identity-1R3bqa3R.js +0 -1
  424. data/public/railwatch/assets/percentile-picker-73z5PAps.js +0 -1
  425. data/public/railwatch/assets/relative-time-Dsc_cCdX.js +0 -1
  426. data/public/railwatch/assets/release-health-BV5OaOWd.js +0 -1
  427. data/public/railwatch/assets/route-CWh58C7K.js +0 -1
  428. data/public/railwatch/assets/segmented-DB4zh-Am.js +0 -1
  429. data/public/railwatch/assets/select-DBKjFlOX.js +0 -1
  430. data/public/railwatch/assets/series-chart-BaiDG795.js +0 -1
  431. data/public/railwatch/assets/show-B4ICCFGh.js +0 -1
  432. data/public/railwatch/assets/show-B5lOU9Gv.js +0 -1
  433. data/public/railwatch/assets/show-BYPB74t4.js +0 -2
  434. data/public/railwatch/assets/show-C7A0m2M8.js +0 -1
  435. data/public/railwatch/assets/show-DT5X1zqE.js +0 -1
  436. data/public/railwatch/assets/show-D_NBdXsn.js +0 -2
  437. data/public/railwatch/assets/show-DgPCnRg8.js +0 -1
  438. data/public/railwatch/assets/show-Yl69HF0x.js +0 -1
  439. data/public/railwatch/assets/show-_QxuUQCm.js +0 -3
  440. data/public/railwatch/assets/show-g__jnN36.js +0 -1
  441. data/public/railwatch/assets/show-l4ztfjl9.js +0 -1
  442. data/public/railwatch/assets/sort-header-BcG8ulEH.js +0 -1
  443. data/public/railwatch/assets/source-link-B2183i2-.js +0 -1
  444. data/public/railwatch/assets/sparkline-cell-B47ipscB.js +0 -1
  445. data/public/railwatch/assets/stat-DT2hhsqS.js +0 -1
  446. data/public/railwatch/assets/status-badge-CmbRKlY7.js +0 -1
  447. data/public/railwatch/assets/tenant-path-C4rNGauE.js +0 -1
  448. data/public/railwatch/assets/use-clipboard-oTNeP4ax.js +0 -1
  449. data/public/railwatch/assets/use-live-CLZZj245.js +0 -1
  450. data/public/railwatch/icon.png +0 -0
  451. data/public/railwatch/icon.svg +0 -5
  452. data/public/railwatch/manifest.json +0 -1245
  453. data/public/railwatch/rails-vite.json +0 -1
  454. /data/{app/frontend/lib → lib/generators/railwatch/install/templates}/railwatch.ts +0 -0
@@ -1,18 +0,0 @@
1
- import type { ReactNode } from "react"
2
-
3
- import { Toaster } from "@/components/ui/sonner"
4
- import { useFlash } from "@/hooks/use-flash"
5
-
6
- interface PersistentLayoutProps {
7
- children: ReactNode
8
- }
9
-
10
- export default function PersistentLayout({ children }: PersistentLayoutProps) {
11
- useFlash()
12
- return (
13
- <>
14
- {children}
15
- <Toaster position="bottom-right" offset={16} />
16
- </>
17
- )
18
- }
@@ -1,10 +0,0 @@
1
- import { type Consumer, createConsumer } from "@rails/actioncable"
2
-
3
- let consumer: Consumer | undefined
4
-
5
- // One ActionCable consumer (one WebSocket) shared by the whole app, opened
6
- // lazily on first subscription rather than on every page load.
7
- export function getConsumer(): Consumer {
8
- consumer ??= createConsumer()
9
- return consumer
10
- }
@@ -1,236 +0,0 @@
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
- ]
@@ -1,55 +0,0 @@
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
- }
@@ -1,50 +0,0 @@
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
- }
@@ -1,25 +0,0 @@
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
- }
@@ -1,155 +0,0 @@
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
- }
@@ -1,71 +0,0 @@
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
- }
@@ -1,18 +0,0 @@
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
- }
@@ -1,23 +0,0 @@
1
- import { configure } from "@/routes"
2
-
3
- // The path the dashboard is served under, from the layout's
4
- // <meta name="railwatch-mount">: the embedded engine's mount point
5
- // (request.script_name), or the base path of a Railwatch server mounted
6
- // inside another app. "" without the tag (the hosted platform) or for
7
- // anything that is not one absolute path: "//host" or "https://..." would
8
- // point every link at another origin.
9
- export function mountPath(doc: Document = document): string {
10
- const raw =
11
- doc.querySelector<HTMLMetaElement>('meta[name="railwatch-mount"]')
12
- ?.content ?? ""
13
- const path = raw.trim().replace(/\/+$/, "")
14
- return /^\/(?![/\\])/.test(path) ? path : ""
15
- }
16
-
17
- // Every route helper carries the mount point (js-routes' script_name).
18
- // Returns the mount, "" when there is none and nothing changed.
19
- export function applyMount(doc: Document = document): string {
20
- const mount = mountPath(doc)
21
- if (mount) configure({ default_url_options: { script_name: mount } })
22
- return mount
23
- }