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
data/docs/testing.md CHANGED
@@ -27,20 +27,12 @@ require "railwatch/minitest"
27
27
  ```
28
28
 
29
29
  Railwatch must be *enabled* in the test environment or every block would look
30
- empty. An embedded install (`transport = :local`) is enabled with no token. A
31
- cloud install needs a token present, so set any non-blank `RAILWATCH_TOKEN` for
32
- the test env. Either way records go to an in-memory transport, never over the
33
- network or into the telemetry database. If Railwatch is disabled, the matchers
34
- raise `Railwatch::SpecHelper::Disabled` rather than quietly passing.
35
-
36
- An embedded install adds its two databases to the `test` environment too, and
37
- Rails' schema check refuses to run the suite while their migrations are
38
- pending. `bin/rails db:test:prepare` does not create them, because they keep no
39
- schema file; run this once locally and as a CI setup step:
40
-
41
- ```sh
42
- RAILS_ENV=test bin/rails db:prepare
43
- ```
30
+ empty, and it is enabled only with a token present: set any non-blank
31
+ `RAILWATCH_TOKEN` for the test env. railwatch-server is off under
32
+ `Rails.env.test` and does not set one for you. Records go to an
33
+ in-memory transport, never over the network, and there is no database to
34
+ prepare. If Railwatch is disabled, the matchers raise
35
+ `Railwatch::SpecHelper::Disabled` rather than quietly passing.
44
36
 
45
37
  Sampling is forced on for the block, so a fractional `c.sample` in the app's
46
38
  test config can't turn an assertion into one that never fires either.
@@ -8,29 +8,9 @@ below are keyed to those lines.
8
8
  bin/rails railwatch:doctor
9
9
  ```
10
10
 
11
- The first lines depend on the transport. An embedded install checks its
12
- databases, writer and dashboard; a cloud install checks its token and
13
- ingest host. The task exits non-zero only on the lines marked fatal
14
- below. Everything else is informational: a `✗` there means a feature
15
- isn't wired, not that the install is broken.
16
-
17
- Embedded (`transport = :local`):
18
-
19
- | Doctor line | What a `✗` means |
20
- |---|---|
21
- | `export` | Only shown with `export_enabled` on. Export is on and cannot work: no token, no URL, a plain-HTTP URL, or an unsupported policy. Fatal. |
22
- | `export destination` | The export queue is blocked or deferred; the line gives the reason. `bin/rails railwatch:export:rebind` clears a credential block. |
23
- | `railwatch database`, `railwatch_telemetry database` | The database is missing from `config/database.yml` for this environment. Fatal. Re-run the install generator. |
24
- | `railwatch migrations`, `railwatch_telemetry migrations` | Migrations are pending. Fatal. Run `bin/rails db:prepare`. |
25
- | `telemetry disk` | The telemetry database is not in incremental auto-vacuum, so pruning never shrinks the file. See [Embedded mode](embedded.md#giving-the-disk-back). |
26
- | `maintenance` | No maintenance tick in the last ten minutes. Expected when the app is stopped: the clock runs in the app's processes, not in rake. |
27
- | `writer process` | The writer socket is not answering, `plugin :railwatch` is missing from `config/puma.rb`, or the socket path is over Linux's 108-byte limit. Expected when the app is stopped. |
28
- | `last write` | Shown when the writer answers: nothing written in five minutes. With the app serving traffic, the writer is stuck or workers are not reaching it. |
29
- | `dashboard access` | HTTP Basic is on with no credentials outside development, so every page is 401; or Basic is off and nothing else is declared. Run `bin/rails railwatch:authentication:configure`, or see [Embedded mode](embedded.md#authentication). |
30
- | `json compatibility` | The installed `json` gem cannot decode on this Rails; see [below](#binjobs-dies-in-a-loop-with-wrong-number-of-arguments-given-2-expected-1). |
31
- | `recurring.yml` | `config/recurring.yml` still lists `Railwatch::*` jobs from a pre-release. Remove them. |
32
-
33
- Cloud (`transport = :http`):
11
+ The task exits non-zero only on the lines marked fatal below. Everything
12
+ else is informational: a `✗` there means a feature isn't wired, not that
13
+ the install is broken.
34
14
 
35
15
  | Doctor line | What a `✗` means |
36
16
  |---|---|
@@ -39,11 +19,6 @@ Cloud (`transport = :http`):
39
19
  | `ingest url` | `ingest_url` isn't a parseable HTTP(S) URL. |
40
20
  | `ingest transport security` | The ingest URL is plain HTTP on a non-loopback host without `RAILWATCH_ALLOW_HTTP=true`. |
41
21
  | `ingest reachable` | `GET {ingest_url}/ingest/ping` didn't return success. Fatal. The ping carries the token, so a missing or wrong token fails this line too; fix `token` first. |
42
-
43
- Both:
44
-
45
- | Doctor line | What a `✗` means |
46
- |---|---|
47
22
  | `request middleware` | `Railwatch::Middleware::Request` isn't in the stack, so requests aren't executions. |
48
23
  | `engine mounted` | `mount Railwatch::Engine, at: "/railwatch"` is missing from `config/routes.rb`; the browser beacon has nowhere to post. |
49
24
  | `deploy` | `config.deploy` is unset — records ship, charts get no deploy markers. |
@@ -64,32 +39,29 @@ app takes.
64
39
  Work down this list. Most of it is the same root cause seen from
65
40
  different angles: Railwatch decided not to record.
66
41
 
67
- **Embedded: the writer is not writing.** Run `railwatch:doctor` with the
68
- app serving traffic. `writer process` and `last write` say whether the
69
- writer is up and when it last wrote; the migrations lines catch a
70
- database that was never prepared.
71
-
72
- **Cloud: the token is missing or blank.** With `transport = :http`,
42
+ **The token is missing or blank.**
73
43
  `Railwatch.enabled?` is `config.enabled && token.present?`. With no token
74
44
  the engine's `railwatch.subscribe` initializer returns early, so no
75
45
  subscribers and no patches are installed at all. Fix: set
76
46
  `RAILWATCH_TOKEN`, restart, and re-run `railwatch:doctor`. The `token`
77
47
  line prints the first 6 characters and the length, which is enough to
78
- spot a truncated or quoted value.
48
+ spot a truncated or quoted value. With railwatch-server installed, the
49
+ token is set when the server's data directory is provisioned; its boot
50
+ log line says whether the agent was wired.
79
51
 
80
- **Cloud: the token is wrong.** A 401 from the ingest marks the transport
52
+ **The token is wrong.** A 401 from the ingest marks the transport
81
53
  permanently unauthorized: no further flush is attempted for the lifetime
82
54
  of that process. Fixing the env var isn't enough. Restart the process.
83
55
  `railwatch:doctor`'s `ingest reachable` line catches this before you
84
56
  deploy.
85
57
 
86
- **Cloud: `RAILWATCH_INGEST_URL` points somewhere else.** Records go where
58
+ **`RAILWATCH_INGEST_URL` points somewhere else.** Records go where
87
59
  you sent them. `railwatch:status` prints the URL it is actually using.
88
- Compare it against the platform you're looking at. Self-hosting: see
60
+ Compare it against the server you're looking at. Self-hosting: see
89
61
  [`self-hosting.md`](self-hosting.md).
90
62
 
91
63
  **`config.enabled` is false.** `RAILWATCH_ENABLED=0` (or `false`/`no`/`off`)
92
- turns everything off, embedded or not.
64
+ turns everything off.
93
65
 
94
66
  **Sample rates are at zero.** `c.sample = { requests: 0.0 }` means no
95
67
  request records. So does the per-route `railwatch_never_sample` macro on
@@ -329,13 +301,7 @@ hook below.
329
301
 
330
302
  **Cause and fix**, in the order the hook itself checks:
331
303
 
332
- - **Embedded: `RAILWATCH_TRANSPORT=local` isn't exported to the hook.**
333
- The hook reads the deployer's environment, not your initializer. With
334
- that variable set it runs `bin/rails railwatch:deploy[$KAMAL_VERSION]`
335
- in the primary container, which writes the marker to the embedded
336
- database. Without it, the hook treats the install as a cloud one and
337
- exits at the next check, since an embedded install has no token.
338
- - **Cloud: `RAILWATCH_TOKEN` isn't exported to the hook.** The next thing
304
+ - **`RAILWATCH_TOKEN` isn't exported to the hook.** The first thing
339
305
  `.kamal/hooks/post-deploy` does is `[ -z "$RAILWATCH_TOKEN" ] && exit 0`.
340
306
  The hook runs on the deployer machine, in your shell, not in a
341
307
  container. So a token that only exists in `.kamal/secrets` for the
@@ -9,17 +9,15 @@ module Railwatch
9
9
  source_root File.expand_path("templates", __dir__)
10
10
 
11
11
  desc "Creates config/initializers/railwatch.rb, a Kamal post-deploy hook, the browser client, and wires the test helpers. " \
12
- "By default telemetry stays in this app, in two SQLite databases, with the dashboard at /railwatch. " \
13
- "--cloud (or any token or URL option) sends it to Railwatch Cloud instead."
12
+ "Telemetry goes to Railwatch Cloud, or to a self-hosted Railwatch server with --url. " \
13
+ "To run the server inside this app instead, add the railwatch-server gem."
14
14
 
15
- class_option :cloud, type: :boolean, default: false,
16
- desc: "Send telemetry to Railwatch Cloud instead of keeping it in this app. Implied by --prompt-token, --token-stdin, --url and --kamal-secrets."
17
15
  class_option :prompt_token, type: :boolean, default: false,
18
16
  desc: "Prompt for the ingest token without echoing it."
19
17
  class_option :token_stdin, type: :boolean, default: false,
20
18
  desc: "Read the ingest token from one line on standard input."
21
19
  class_option :url, type: :string,
22
- desc: "Ingest URL, for a self-hosted Railwatch Cloud. Defaults to https://railwatch.rebulk.com."
20
+ desc: "Ingest URL, for a self-hosted Railwatch server. Defaults to https://railwatch.rebulk.com."
23
21
  class_option :kamal_secrets, type: :boolean, default: false,
24
22
  desc: "Wire RAILWATCH_TOKEN through .kamal/secrets and config/deploy.yml's `env: secret:` list."
25
23
  class_option :doctor, type: :boolean, default: true,
@@ -59,59 +57,7 @@ module Railwatch
59
57
  append_to_file "Gemfile", "#{contents.end_with?("\n") ? "" : "\n"}\n" \
60
58
  "# #{Railwatch::JsonCompat::ISSUE}: Rails cannot decode with json 3 yet. Remove when it can.\n" \
61
59
  "#{Railwatch::JsonCompat::PIN}\n"
62
- @needs_bundle = true
63
- say "#{Railwatch::JsonCompat.advice} Added the pin to your Gemfile; run `bundle install`, then " \
64
- "`bin/rails db:prepare`.", :yellow
65
- end
66
-
67
- # Embedded mode stores telemetry in SQLite files whatever the app's own
68
- # database is, so an app on PostgreSQL or MySQL needs the adapter gem
69
- # added before those files can be created.
70
- def ensure_sqlite3_gem
71
- return unless local?
72
- return if Gem.loaded_specs.key?("sqlite3")
73
- return say("Embedded mode needs the sqlite3 gem for its two databases; add `gem \"sqlite3\"` and re-run.", :yellow) unless File.exist?("Gemfile")
74
-
75
- contents = File.read("Gemfile")
76
- unless contents.match?(/^\s*gem ["']sqlite3["']/)
77
- append_to_file "Gemfile", %(#{contents.end_with?("\n") ? "" : "\n"}\n# Railwatch (embedded) keeps its telemetry in two SQLite files of its own.\ngem "sqlite3"\n)
78
- end
79
- @needs_bundle = true
80
- say "Added `gem \"sqlite3\"` to the Gemfile: Railwatch's two databases are SQLite files whatever this app's " \
81
- "own database is. Run `bundle install`, then `bin/rails db:prepare` to create them.", :yellow
82
- end
83
-
84
-
85
- # Two databases of its own, never the app's primary: `railwatch` for
86
- # what people author (issues, comments, saved views, thresholds) and
87
- # `railwatch_telemetry` for what the app reports, which is written
88
- # continuously and pruned. Their migrations live in the gem; the
89
- # database entries point migrations_paths at them, so db:prepare
90
- # creates the tables now and migrates them after every gem update.
91
- # Nothing is copied into the app.
92
- def configure_local_databases
93
- return unless local?
94
-
95
- return say("No config/database.yml found; add railwatch and railwatch_telemetry databases yourself (docs/embedded.md).", :yellow) unless File.exist?("config/database.yml")
96
-
97
- contents = File.read("config/database.yml")
98
- updated = self.class.database_yml_with_railwatch(contents)
99
- return say_status(:identical, "config/database.yml", :blue) if updated == contents
100
-
101
- create_file "config/database.yml", updated, force: true
102
- end
103
-
104
- # The writer process: one per Puma master, forked by the gem's Puma
105
- # plugin, so batches are mapped and written outside the web workers.
106
- def configure_local_writer
107
- return unless local?
108
- return say("No config/puma.rb found; add `plugin :railwatch` to your Puma config yourself (docs/embedded.md).", :yellow) unless File.exist?("config/puma.rb")
109
-
110
- contents = File.read("config/puma.rb")
111
- updated = self.class.puma_rb_with_railwatch(contents)
112
- return say_status(:identical, "config/puma.rb", :blue) if updated == contents
113
-
114
- create_file "config/puma.rb", updated, force: true
60
+ say "#{Railwatch::JsonCompat.advice} Added the pin to your Gemfile; run `bundle install`.", :yellow
115
61
  end
116
62
 
117
63
  def create_kamal_hook
@@ -120,10 +66,9 @@ module Railwatch
120
66
  chmod ".kamal/hooks/post-deploy", 0o755
121
67
  end
122
68
 
123
- # The one browser client, the same file the gem's own dashboard bundle
124
- # is built from (Railwatch.browser_client_path). Copied rather than
125
- # templated: there is nothing to interpolate, and a copy means the host
126
- # sees exactly what the gem tests.
69
+ # The one browser client (Railwatch.browser_client_path). Copied rather
70
+ # than templated: there is nothing to interpolate, and a copy means the
71
+ # host sees exactly what the gem tests.
127
72
  def create_browser_client
128
73
  return unless File.directory?("app/frontend")
129
74
  copy_file Railwatch.browser_client_path.to_s, "app/frontend/lib/railwatch.ts"
@@ -167,8 +112,6 @@ module Railwatch
167
112
  # A token lands in .env only when Git confirms the file is ignored.
168
113
  # URLs are not secret and can still be written to a tracked dotenv file.
169
114
  def write_env
170
- return if local?
171
-
172
115
  token = resolved_token
173
116
  vars = { TOKEN_VAR => token, URL_VAR => options[:url] }.compact
174
117
  return if vars.empty?
@@ -215,58 +158,7 @@ module Railwatch
215
158
  create_file "config/deploy.yml", updated, force: true
216
159
  end
217
160
 
218
- # The two databases exist and are migrated when the generator returns:
219
- # config/database.yml was just rewritten, so this re-reads it and runs
220
- # the same prepare a deploy runs, for both databases only. The host's
221
- # own databases are not touched, and a schema file is never written.
222
- def prepare_local_databases
223
- return unless local?
224
- return unless File.exist?("config/database.yml")
225
- return if @needs_bundle
226
- return unless defined?(Rails) && Rails.respond_to?(:application) && Rails.application
227
-
228
- say "\nbin/rails db:prepare (railwatch, railwatch_telemetry)", :green
229
- require "active_record"
230
- ActiveRecord::Base.configurations = Rails.application.config.database_configuration
231
- %w[railwatch railwatch_telemetry].each do |name|
232
- db_config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: name)
233
- next say(" #{name}: not in config/database.yml for #{Rails.env}", :yellow) unless db_config
234
-
235
- ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: Rails.env, name: name) do
236
- ActiveRecord::Tasks::DatabaseTasks.migrate
237
- end
238
- say_status :prepared, "#{name} (#{db_config.database})", :green
239
- end
240
- @prepared = true
241
- rescue StandardError => e
242
- say "Could not prepare the Railwatch databases here (#{e.class}: #{e.message}). Run `bin/rails db:prepare` yourself.", :yellow
243
- end
244
-
245
161
  def show_next_steps
246
- if local?
247
- say <<~STEPS, :green
248
-
249
- Next steps
250
- 1. Restart the app and open /railwatch. In development it is open.
251
- 2. Before production, give it a password (it is closed there until
252
- you do): RAILS_ENV=production bin/rails railwatch:authentication:configure
253
- Using your own admin auth instead? See docs/embedded.md,
254
- Authentication (base_controller_class or a routes constraint).
255
- 3. #{@prepared ? "Nothing else to run. Both databases were created just now and" : "Create the two databases: bin/rails db:prepare\n Then"}
256
- `bin/rails db:prepare` (which a deploy already runs) migrates
257
- them after every gem update. With `plugin :railwatch` in
258
- config/puma.rb Puma forks one Railwatch writer process that
259
- writes every batch and runs the maintenance clock, so no web
260
- process ever holds the telemetry database. No job worker.
261
- 4. Optional: mirror to Railwatch Cloud for alerts delivered even
262
- when this app is down, MCP for your AI assistant, and every
263
- app in one place. Get a token (bin/rails railwatch:token), set
264
- RAILWATCH_TOKEN, then c.export_enabled = true in the
265
- initializer (docs/embedded.md).
266
- STEPS
267
- return
268
- end
269
-
270
162
  say <<~STEPS, :green
271
163
 
272
164
  Next steps
@@ -281,6 +173,11 @@ module Railwatch
281
173
  2. Verify the install: bin/rails railwatch:doctor
282
174
  3. Connect your AI assistant: bin/rails railwatch:mcp
283
175
  4. Gate performance in CI: see docs/testing.md.
176
+
177
+ Want the dashboard inside this app instead, with no token? Add the
178
+ railwatch-server gem (gem "railwatch-server", then
179
+ `plugin :railwatch_server` in config/puma.rb):
180
+ https://github.com/Rebulk/railwatch-server
284
181
  STEPS
285
182
  end
286
183
 
@@ -290,10 +187,6 @@ module Railwatch
290
187
  # note below says so rather than letting a ✗ look like a broken install.
291
188
  def run_doctor
292
189
  return unless options[:doctor]
293
- # This process read its configuration before the initializer was
294
- # written, so in local mode the doctor would report an http transport
295
- # with no token. The databases it would check were prepared above.
296
- return if local?
297
190
  return unless defined?(Rails) && Rails.respond_to?(:application) && Rails.application
298
191
 
299
192
  say "\nbin/rails railwatch:doctor", :green
@@ -327,117 +220,6 @@ module Railwatch
327
220
  insert_lines(lines, block_end(lines, secret_start), " - #{name}\n")
328
221
  end
329
222
 
330
- # Spelled out rather than `<<: *default`, on purpose. The host's default
331
- # block carries ITS adapter: on a PostgreSQL or MySQL app inheriting it
332
- # would ask that server for a database called
333
- # "storage/production_railwatch.sqlite3". These two are always SQLite
334
- # files the app owns, whatever the app's own database is, so they name
335
- # their adapter themselves -- which also means the file needs no
336
- # `&default` anchor to exist at all.
337
- RAILWATCH_DATABASES = <<~YAML
338
- railwatch:
339
- adapter: sqlite3
340
- database: storage/%<env>s_railwatch.sqlite3
341
- pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %%>
342
- timeout: 5000
343
- migrations_paths: <%%= Railwatch.migrations_path(:railwatch) %%>
344
- schema_dump: false
345
- railwatch_telemetry:
346
- adapter: sqlite3
347
- database: storage/%<env>s_railwatch_telemetry.sqlite3
348
- pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %%>
349
- timeout: 5000
350
- migrations_paths: <%%= Railwatch.migrations_path(:railwatch_telemetry) %%>
351
- schema_dump: false
352
- pragmas:
353
- journal_mode: wal
354
- synchronous: normal
355
- mmap_size: 134217728
356
- cache_size: -65536
357
- temp_store: memory
358
- YAML
359
-
360
- # Adds the railwatch and railwatch_telemetry databases to every
361
- # environment in config/database.yml. A flat environment
362
- # (`development:` straight to `<<: *default`) becomes a `primary:`
363
- # entry first, since named databases need the nested form. Text
364
- # insertion rather than a YAML round trip, for the same reason as
365
- # deploy_yml_with_secret: the comments are most of the file.
366
- def self.database_yml_with_railwatch(contents)
367
- lines = contents.lines
368
- # Rails 8.1's non-Docker template leaves every production `database:`
369
- # commented out ("path/to/persistent/storage/..."), so db:prepare
370
- # cannot run in production at all until the host fills them in. The
371
- # storage/ paths are what the Docker template writes and what
372
- # config/deploy.yml mounts; use them.
373
- lines = lines.map do |line|
374
- line.sub(%r{\A(\s+)# database: path/to/persistent/storage/(\S+)$}, '\1database: storage/\2')
375
- end
376
- environments_in(lines).each do |env|
377
- start = lines.index { |line| line.match?(/\A#{env}:\s*(#.*)?$/) }
378
- next unless start
379
-
380
- stop = block_end(lines, start)
381
- block = lines[(start + 1)...stop]
382
- next if block.any? { |line| line.match?(/\A\s+railwatch_telemetry:\s*$/) }
383
-
384
- nested = block.any? { |line| line.match?(/\A [a-z_]+:\s*$/) }
385
- unless nested
386
- lines[(start + 1)...stop] = block.map { |line| line.strip.empty? ? line : " #{line}" }
387
- lines.insert(start + 1, " primary:\n")
388
- stop += 1
389
- end
390
- entries = format(RAILWATCH_DATABASES, env: database_prefix(env)).lines.map { |line| " #{line}" }
391
- lines = insert_lines(lines, stop, entries.join).lines
392
- end
393
- lines.join
394
- end
395
-
396
- # What the database filenames are built from. For test that has to carry
397
- # TEST_ENV_NUMBER, the way Rails' own test database naming does: runners
398
- # like parallel_tests give each worker its own number and expect a
399
- # database per worker. Without it every worker in a run opens the same
400
- # two SQLite files and they fight over them -- observed as
401
- # PendingMigrationError and "disk I/O error" from configure_connection,
402
- # on an app running four workers. An app with no such runner sets no
403
- # TEST_ENV_NUMBER, so the suffix is empty and the name is unchanged.
404
- def self.database_prefix(env)
405
- return env unless env == "test"
406
-
407
- %(test<%= ENV["TEST_ENV_NUMBER"] %>)
408
- end
409
-
410
- # Unconditional: `bundle exec puma` evaluates this file before it loads
411
- # the Rack app, so `if defined?(Railwatch)` would be false there and the
412
- # writer would never start. The plugin itself is inert when Railwatch is
413
- # absent, off, or not in embedded mode.
414
- PUMA_PLUGIN_LINES = <<~RUBY
415
-
416
- # Railwatch (embedded): fork one writer process from Puma so telemetry
417
- # is mapped and written outside the processes serving requests.
418
- plugin :railwatch
419
- RUBY
420
-
421
- # Appends the plugin line once. Puma's config is plain Ruby evaluated top
422
- # to bottom, so the end of the file is always a valid place for it.
423
- def self.puma_rb_with_railwatch(contents)
424
- return contents if contents.include?("plugin :railwatch")
425
-
426
- "#{contents.sub(/\n*\z/, "\n")}#{PUMA_PLUGIN_LINES}"
427
- end
428
-
429
- # Every environment the file defines, not a fixed three: an app with a
430
- # `staging` (or `review`, or `qa`) environment needs the databases
431
- # there too, and hardcoding names silently left it without them. A
432
- # top-level key with a block under it, minus YAML's own anchors.
433
- NON_ENVIRONMENT_KEYS = %w[default shared].freeze
434
-
435
- def self.environments_in(lines)
436
- lines.filter_map { |line| line[/\A([a-z_][a-z0-9_]*):\s*(?:&\S+\s*)?(?:#.*)?$/, 1] }
437
- .reject { |name| NON_ENVIRONMENT_KEYS.include?(name) }
438
- .uniq
439
- end
440
-
441
223
  # Index of the first line after the block opened at `start`: the next
442
224
  # line indented no more deeply than it, ignoring blanks and comments,
443
225
  # then backed up over any trailing blank lines so an insertion lands
@@ -461,16 +243,6 @@ module Railwatch
461
243
 
462
244
  private
463
245
 
464
- # Embedded unless the invocation asks for the cloud: --cloud itself, or
465
- # an option that only means something there. A RAILWATCH_TOKEN already
466
- # in the environment is not asking -- it is picked up when one of these
467
- # is given, never used to choose the mode.
468
- CLOUD_OPTIONS = %i[cloud prompt_token token_stdin url kamal_secrets].freeze
469
-
470
- def local?
471
- CLOUD_OPTIONS.none? { |name| options[name] }
472
- end
473
-
474
246
  def resolved_token
475
247
  @resolved_token ||= begin
476
248
  value = if options[:prompt_token]
@@ -3,47 +3,8 @@
3
3
  # Railwatch: first-class monitoring for Rails. Every option here can also be
4
4
  # set by the RAILWATCH_* env var named in the comment.
5
5
  Railwatch.configure do |c|
6
- <% if local? -%>
7
- # Telemetry stays in this app's own railwatch_telemetry database and the
8
- # dashboard is served at /railwatch. No token, no cloud. Who can open the
9
- # dashboard is the Access block below.
10
- c.transport = :local # RAILWATCH_TRANSPORT
11
- c.ignored_request_paths += ["/railwatch", %r{\A/railwatch/}]
12
- # c.issue_prefix = "APP" # RAILWATCH_ISSUE_PREFIX; issue keys like APP-12
13
- # c.repository_url = "https://github.com/you/app" # RAILWATCH_REPOSITORY_URL; source links from stack traces
14
- # c.retention_days = 7 # RAILWATCH_RETENTION_DAYS; PruneTelemetryJob keeps this much
15
- # Access. The dashboard shows every query, log line and exception this app
16
- # records, so pick one of these. Out of the box it is HTTP Basic: open in
17
- # development, closed everywhere else until credentials exist:
18
- # RAILS_ENV=production bin/rails railwatch:authentication:configure
19
- # or RAILWATCH_HTTP_BASIC_AUTH_USER / _PASSWORD.
20
- #
21
- # Using your own admin auth instead? Turn Basic off and say which, so the
22
- # gem knows the mount is covered and live updates can follow your rule:
23
- # c.http_basic_auth_enabled = false # RAILWATCH_HTTP_BASIC_AUTH_ENABLED
24
- # c.base_controller_class = "AdminController" # every dashboard page inherits it
25
- # c.dashboard_user = ->(request) { ... } # or decide per request, below
26
- #
27
- # Genuinely public (a private network, a VPN, a constraint the gem cannot
28
- # see)? Say so on purpose. Nothing else makes the dashboard public, and
29
- # without one of the above the app logs a warning at every boot:
30
- # c.dashboard_open = false # RAILWATCH_DASHBOARD_OPEN
31
- # Who the dashboard names on comments and saved views. Returning nil also
32
- # refuses the request, so this doubles as your authorisation rule:
33
- # c.dashboard_user = ->(request) { user = Current.user; user&.admin? && { id: user.id, name: user.name, email: user.email } }
34
-
35
- # The browser beacon (Inertia visit timings and JavaScript errors) is a
36
- # public endpoint, like every browser telemetry endpoint: it is bounded by
37
- # an origin allowlist, a per-client rate limit and a ceiling for the whole
38
- # endpoint, not by a credential.
39
- # c.beacon_enabled = false # RAILWATCH_BEACON
40
- # c.beacon_allowed_origins = ["https://app.example.com"] # beyond this app's own
41
- # c.beacon_rate_limit = 120 # per client IP per minute
42
- # c.beacon_global_rate_limit = 6_000 # for the endpoint, per minute
43
- <% else -%>
44
6
  # c.token = ENV["RAILWATCH_TOKEN"] # RAILWATCH_TOKEN (required)
45
- <% end -%>
46
- # c.ingest_url = "https://railwatch.rebulk.com" # RAILWATCH_INGEST_URL
7
+ # c.ingest_url = "https://railwatch.rebulk.com" # RAILWATCH_INGEST_URL; your own server when self-hosting
47
8
  # c.deploy = "release-name" # RAILWATCH_DEPLOY; platform/Git auto-detected
48
9
  # c.detect_deploy = false # RAILWATCH_DETECT_DEPLOY; default true
49
10
 
@@ -75,6 +36,15 @@ Railwatch.configure do |c|
75
36
  # c.redact_params += %w[ssn] # merged with Rails.application.config.filter_parameters
76
37
  # c.ignored_request_paths += ["/internal/health"] # /up and /railwatch/beacon are ignored by default
77
38
 
39
+ # The browser beacon (Inertia visit timings and JavaScript errors) is a
40
+ # public endpoint, like every browser telemetry endpoint: it is bounded by
41
+ # an origin allowlist, a per-client rate limit and a ceiling for the whole
42
+ # endpoint, not by a credential.
43
+ # c.beacon_enabled = false # RAILWATCH_BEACON
44
+ # c.beacon_allowed_origins = ["https://app.example.com"] # beyond this app's own
45
+ # c.beacon_rate_limit = 120 # per client IP per minute
46
+ # c.beacon_global_rate_limit = 6_000 # for the endpoint, per minute
47
+
78
48
  # How the current user is described. Default reads Current.user then Warden.
79
49
  # c.user { |user| { id: user.id, name: user.name, email: user.email } }
80
50
  end
@@ -9,13 +9,6 @@
9
9
  # ingest host instead of shelling into a container. Never fails a deploy.
10
10
  set -e
11
11
 
12
- # Embedded install (transport = :local): the marker is a row in the app's own
13
- # railwatch database, so record it inside the deployed container and stop.
14
- if [ "${RAILWATCH_TRANSPORT:-}" = "local" ]; then
15
- bin/kamal app exec --primary --reuse "bin/rails railwatch:deploy[$KAMAL_VERSION]" || true
16
- exit 0
17
- fi
18
-
19
12
  [ -z "$RAILWATCH_TOKEN" ] && exit 0
20
13
 
21
14
  # Optional: .kamal/hooks/post-deploy --sourcemaps, or set
@@ -3,11 +3,12 @@
3
3
  require "yaml"
4
4
 
5
5
  module Railwatch
6
- # `bin/rails railwatch:authentication:configure`: stores the embedded
7
- # dashboard's HTTP Basic credentials in the current environment's Rails
8
- # credentials, the same way Mission Control Jobs' authentication:configure
9
- # does. Asks for a user, generates the password, appends a `railwatch:`
10
- # entry to the credentials file the environment reads.
6
+ # `bin/rails railwatch:authentication:configure`: stores the HTTP Basic
7
+ # credentials railwatch-server's dashboard proxy checks in the current
8
+ # environment's Rails credentials, the same way Mission Control Jobs'
9
+ # authentication:configure does. Asks for a user, generates the password,
10
+ # appends a `railwatch:` entry to the credentials file the environment
11
+ # reads.
11
12
  module Authentication
12
13
  module_function
13
14