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