railwatch 0.9.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (452) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +37 -33
  3. data/CHANGELOG.md +50 -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 +21 -151
  21. data/lib/railwatch/engine.rb +9 -118
  22. data/lib/railwatch/patches.rb +0 -11
  23. data/lib/railwatch/reporter.rb +3 -19
  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,217 +0,0 @@
1
- // Builds "open this file" URLs: either into the application's repository on
2
- // GitHub/GitLab (pinned to the deploy's git SHA when we have one, else the
3
- // default branch) or into the user's local editor via its URL scheme. The
4
- // repository URL comes from the application, the editor and local checkout
5
- // path from the user's profile.
6
-
7
- export interface SourceUrlOptions {
8
- file: string
9
- line?: number | null
10
- repositoryUrl?: string | null
11
- ref?: string | null
12
- defaultBranch?: string | null
13
- editor?: string | null
14
- editorRoot?: string | null
15
- }
16
-
17
- function present(value: string | null | undefined): string | null {
18
- const trimmed = (value ?? "").trim()
19
- return trimmed === "" ? null : trimmed
20
- }
21
-
22
- function hasControlCharacters(value: string): boolean {
23
- return [...value].some((character) => {
24
- const code = character.codePointAt(0) ?? 0
25
- return code <= 0x1f || (code >= 0x7f && code <= 0x9f)
26
- })
27
- }
28
-
29
- function decodeToFixedPoint(value: string): string | null {
30
- let decoded = value
31
-
32
- try {
33
- // Each useful decode consumes at least one percent escape, so an
34
- // input-length bound reaches a fixed point without an arbitrary pass cap.
35
- for (let remaining = value.length + 1; remaining > 0; remaining -= 1) {
36
- const next = decodeURIComponent(decoded)
37
- if (next === decoded) return decoded
38
- decoded = next
39
- }
40
- } catch {
41
- return null
42
- }
43
-
44
- return null
45
- }
46
-
47
- function encodePathSegments(value: string): string | null {
48
- try {
49
- return value.split("/").map(encodeURIComponent).join("/")
50
- } catch {
51
- return null
52
- }
53
- }
54
-
55
- function isAbsolutePath(value: string): boolean {
56
- return (
57
- value.startsWith("/") ||
58
- value.startsWith("\\") ||
59
- /^[a-z]:[\\/]/iu.test(value)
60
- )
61
- }
62
-
63
- function sourcePath(value: string | null | undefined): {
64
- raw: string
65
- encoded: string
66
- } | null {
67
- const raw = present(value)
68
- if (!raw || isAbsolutePath(raw)) return null
69
-
70
- const decoded = decodeToFixedPoint(raw)
71
- if (!decoded || isAbsolutePath(decoded)) return null
72
-
73
- if (
74
- decoded.includes("\\") ||
75
- hasControlCharacters(decoded) ||
76
- /%(?:2e|2f|5c)/iu.test(decoded) ||
77
- decoded
78
- .split("/")
79
- .some((segment) => segment === "" || segment === "." || segment === "..")
80
- )
81
- return null
82
-
83
- const encoded = encodePathSegments(raw)
84
- return encoded ? { raw, encoded } : null
85
- }
86
-
87
- function encodedRoot(value: string | null | undefined): {
88
- raw: string
89
- encoded: string
90
- } | null {
91
- const raw = present((value ?? "").replace(/\/+$/, ""))
92
- if (!raw) return null
93
- const encoded = encodePathSegments(raw)
94
- return encoded ? { raw, encoded } : null
95
- }
96
-
97
- function gitRef(value: string | null | undefined): {
98
- raw: string
99
- encoded: string
100
- } | null {
101
- const raw = present(value)
102
- if (
103
- !raw ||
104
- raw === "@" ||
105
- raw.startsWith("/") ||
106
- raw.endsWith("/") ||
107
- raw.endsWith(".") ||
108
- raw.includes("//") ||
109
- raw.includes("..") ||
110
- raw.includes("@{") ||
111
- raw.includes("[") ||
112
- hasControlCharacters(raw) ||
113
- /[ ~^:?*\\]/u.test(raw) ||
114
- raw
115
- .split("/")
116
- .some(
117
- (component) => component.startsWith(".") || component.endsWith(".lock"),
118
- )
119
- )
120
- return null
121
-
122
- try {
123
- return { raw, encoded: encodeURIComponent(raw) }
124
- } catch {
125
- return null
126
- }
127
- }
128
-
129
- // "https://github.com/org/repo.git/" -> "https://github.com/org/repo"
130
- function repository(url: string | null | undefined): string | null {
131
- return present((url ?? "").replace(/\/+$/, "").replace(/\.git$/, ""))
132
- }
133
-
134
- function host(url: string | null | undefined): "github" | "gitlab" | null {
135
- const repo = repository(url)
136
- if (!repo) return null
137
- if (repo.includes("gitlab")) return "gitlab"
138
- if (repo.includes("github")) return "github"
139
- return null
140
- }
141
-
142
- export function sourceUrl({
143
- file,
144
- line,
145
- repositoryUrl,
146
- ref,
147
- defaultBranch,
148
- editor,
149
- editorRoot,
150
- }: SourceUrlOptions): string | null {
151
- const path = sourcePath(file)
152
- if (!path) return null
153
-
154
- if (editor === "github" || editor === "gitlab") {
155
- const repo = repository(repositoryUrl)
156
- const at = gitRef(present(ref) ?? defaultBranch)
157
- if (!repo || !at) return null
158
- const blob = editor === "gitlab" ? "-/blob" : "blob"
159
- return `${repo}/${blob}/${at.encoded}/${path.encoded}${line ? `#L${line}` : ""}`
160
- }
161
-
162
- const root = encodedRoot(editorRoot)
163
- if (!root) return null
164
- const target = `${root.raw}/${path.raw}`
165
- const encodedTarget = `${root.encoded}/${path.encoded}`
166
- const suffix = line ? `:${line}` : ""
167
- switch (editor) {
168
- case "vscode":
169
- return `vscode://file${encodedTarget}${suffix}`
170
- case "cursor":
171
- return `cursor://file${encodedTarget}${suffix}`
172
- case "zed":
173
- return `zed://file${encodedTarget}${suffix}`
174
- case "rubymine":
175
- return `jetbrains://rubymine/navigate/reference?project=&path=${encodeURIComponent(`${target}${suffix}`)}`
176
- case "textmate":
177
- return `txmt://open?url=${encodeURIComponent(`file://${target}`)}${line ? `&line=${line}` : ""}`
178
- default:
179
- return null
180
- }
181
- }
182
-
183
- export function commitUrl(
184
- repositoryUrl: string | null | undefined,
185
- sha: string | null | undefined,
186
- ): string | null {
187
- const repo = repository(repositoryUrl)
188
- const commit = gitRef(sha)
189
- if (!repo || !commit) return null
190
- switch (host(repositoryUrl)) {
191
- case "github":
192
- return `${repo}/commit/${commit.encoded}`
193
- case "gitlab":
194
- return `${repo}/-/commit/${commit.encoded}`
195
- default:
196
- return null
197
- }
198
- }
199
-
200
- export function compareUrl(
201
- repositoryUrl: string | null | undefined,
202
- from: string | null | undefined,
203
- to: string | null | undefined,
204
- ): string | null {
205
- const repo = repository(repositoryUrl)
206
- const start = gitRef(from)
207
- const end = gitRef(to)
208
- if (!repo || !start || !end) return null
209
- switch (host(repositoryUrl)) {
210
- case "github":
211
- return `${repo}/compare/${start.encoded}...${end.encoded}`
212
- case "gitlab":
213
- return `${repo}/-/compare/${start.encoded}...${end.encoded}`
214
- default:
215
- return null
216
- }
217
- }
@@ -1,6 +0,0 @@
1
- import { type ClassValue, clsx } from "clsx"
2
- import { twMerge } from "tailwind-merge"
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }
@@ -1,171 +0,0 @@
1
- import { Link, router, usePage } from "@inertiajs/react"
2
- import { ChevronDown, ChevronRight } from "lucide-react"
3
- import { useState } from "react"
4
-
5
- import { EmptyState } from "@/components/railwatch/empty-state"
6
- import { FilterBar } from "@/components/railwatch/filter-bar"
7
- import { JsonViewer } from "@/components/railwatch/json-viewer"
8
- import { PageHeader } from "@/components/railwatch/page-header"
9
- import { RelativeTime } from "@/components/railwatch/relative-time"
10
- import { Badge } from "@/components/ui/badge"
11
- import { Button } from "@/components/ui/button"
12
- import EnvLayout from "@/layouts/env-layout"
13
- import { parseFilter, serializeFilter } from "@/lib/filter"
14
- import * as R from "@/routes"
15
- import type { SharedProps } from "@/types"
16
-
17
- interface AlertRow {
18
- id: number
19
- event: string
20
- status: string
21
- sent_at: string | null
22
- error: string | null
23
- payload: Record<string, unknown>
24
- created_at: string
25
- integration: { kind: string; name: string }
26
- issue: { id: number; key: string; title: string } | null
27
- }
28
- interface Props {
29
- alerts: AlertRow[]
30
- q: string
31
- statuses: string[]
32
- }
33
-
34
- export default function AlertsIndex(p: Props) {
35
- const { environment } = usePage<SharedProps>().props
36
- const [expanded, setExpanded] = useState<number | null>(null)
37
- const visit = (q: string) => {
38
- const path = environment
39
- ? R.applicationEnvironmentAlertsPath(
40
- environment.application_id,
41
- environment.id,
42
- { q: q || undefined },
43
- )
44
- : R.alertsPath({ q: q || undefined })
45
- router.visit(path, { preserveState: true, preserveScroll: true })
46
- }
47
- const retry = (id: number) =>
48
- router.post(R.retryAlertPath(id), {}, { preserveScroll: true })
49
-
50
- // "Did anything fail to deliver?" is the question this page exists to
51
- // answer after an incident, so it gets a button rather than a token the
52
- // reader has to know how to type.
53
- const parsed = parseFilter(p.q)
54
- const failedOnly = parsed.fields.status === "failed"
55
- const toggleFailed = () => {
56
- const fields = { ...parsed.fields }
57
- if (failedOnly) delete fields.status
58
- else fields.status = "failed"
59
- visit(serializeFilter({ text: parsed.text, fields }))
60
- }
61
-
62
- return (
63
- <EnvLayout title="Alerts" crumbs={[{ title: "Alerts", href: "#" }]}>
64
- <PageHeader
65
- withWindow={false}
66
- title="Alerts"
67
- description="Fired alerts across your integrations, newest first."
68
- />
69
- <div className="flex flex-wrap items-center gap-2">
70
- <FilterBar
71
- value={p.q}
72
- fields={[
73
- {
74
- key: "event",
75
- label: "Event",
76
- options: ["new_issue", "regressed_issue", "threshold", "quota"],
77
- },
78
- { key: "status", label: "Status", options: p.statuses },
79
- ]}
80
- onChange={visit}
81
- placeholder="event:new_issue status:failed"
82
- />
83
- <Button
84
- size="sm"
85
- variant={failedOnly ? "default" : "outline"}
86
- className="h-8"
87
- onClick={toggleFailed}
88
- >
89
- Failed only
90
- </Button>
91
- </div>
92
- {p.alerts.length === 0 ? (
93
- <EmptyState title="No alerts have fired." />
94
- ) : (
95
- <div className="divide-y rounded-md border">
96
- {p.alerts.map((a) => {
97
- const isOpen = expanded === a.id
98
- return (
99
- <div key={a.id} className="p-3">
100
- <button
101
- type="button"
102
- className="flex w-full flex-wrap items-center gap-3 text-left text-sm"
103
- onClick={() => setExpanded(isOpen ? null : a.id)}
104
- >
105
- {isOpen ? (
106
- <ChevronDown className="text-muted-foreground size-4 shrink-0" />
107
- ) : (
108
- <ChevronRight className="text-muted-foreground size-4 shrink-0" />
109
- )}
110
- <span className="font-mono text-xs">{a.event}</span>
111
- <span className="text-muted-foreground text-xs">
112
- {a.integration.name} ({a.integration.kind})
113
- </span>
114
- {a.issue && (
115
- <Link
116
- href={R.issuePath(a.issue.id)}
117
- onClick={(e) => e.stopPropagation()}
118
- className="text-xs hover:underline"
119
- >
120
- <span className="font-mono font-semibold">
121
- {a.issue.key}
122
- </span>{" "}
123
- <span className="text-muted-foreground">
124
- {a.issue.title}
125
- </span>
126
- </Link>
127
- )}
128
- <span className="ml-auto flex items-center gap-2">
129
- <Badge
130
- variant={
131
- a.status === "failed" ? "destructive" : "outline"
132
- }
133
- className="capitalize"
134
- title={a.error ?? undefined}
135
- >
136
- {a.status}
137
- </Badge>
138
- <RelativeTime
139
- iso={a.sent_at ?? a.created_at}
140
- className="text-muted-foreground text-xs"
141
- />
142
- {a.status === "failed" && (
143
- <Button
144
- size="sm"
145
- variant="outline"
146
- onClick={(e) => {
147
- e.stopPropagation()
148
- retry(a.id)
149
- }}
150
- >
151
- Retry
152
- </Button>
153
- )}
154
- </span>
155
- </button>
156
- {isOpen && (
157
- <div className="mt-3 space-y-2 pl-7">
158
- {a.error && (
159
- <p className="text-destructive text-xs">{a.error}</p>
160
- )}
161
- <JsonViewer data={a.payload} />
162
- </div>
163
- )}
164
- </div>
165
- )
166
- })}
167
- </div>
168
- )}
169
- </EnvLayout>
170
- )
171
- }
@@ -1,171 +0,0 @@
1
- import { Link, router, usePage } from "@inertiajs/react"
2
- import { Cable } from "lucide-react"
3
-
4
- import { DataTable } from "@/components/railwatch/data-table"
5
- import { EmptyState } from "@/components/railwatch/empty-state"
6
- import { PageHeader } from "@/components/railwatch/page-header"
7
- import { Badge } from "@/components/ui/badge"
8
- import EnvLayout from "@/layouts/env-layout"
9
- import { bytes, count, ms, when } from "@/lib/format"
10
- import * as R from "@/routes"
11
- import type { SharedProps } from "@/types"
12
-
13
- interface Stream {
14
- group_hash: string
15
- kind: string
16
- name: string
17
- count: number
18
- bytes: number
19
- avg: number
20
- }
21
- interface Recent {
22
- id: number
23
- kind: string
24
- failed: boolean
25
- stream: string | null
26
- channel: string | null
27
- action: string | null
28
- bytes: number | null
29
- duration: number
30
- occurred_at: string
31
- execution_id: string | null
32
- execution_preview: string | null
33
- }
34
- interface Props {
35
- streams: Stream[]
36
- recent: Recent[]
37
- }
38
-
39
- export default function Broadcasts(p: Props) {
40
- const { environment } = usePage<SharedProps>().props
41
- const a = environment!.application_id
42
- const e = environment!.id
43
- const recentHref = (r: Recent) =>
44
- r.execution_id
45
- ? R.applicationEnvironmentRequestPath(a, e, r.execution_id)
46
- : null
47
- return (
48
- <EnvLayout title="Broadcasts">
49
- <PageHeader
50
- title="Broadcasts"
51
- description="Action Cable broadcasts, transmits, and channel actions (covers inertia_cable and Turbo Streams)."
52
- />
53
- <DataTable
54
- rows={p.streams}
55
- rowKey={(s) => `${s.group_hash}-${s.kind}`}
56
- empty={
57
- <EmptyState
58
- icon={Cable}
59
- title="No broadcasts in this window"
60
- description="Action Cable broadcasts appear here as your app sends them."
61
- />
62
- }
63
- columns={[
64
- { key: "k", header: "Kind", cell: (s) => s.kind },
65
- {
66
- key: "n",
67
- header: "Stream / channel",
68
- cell: (s) => <span className="font-mono text-xs">{s.name}</span>,
69
- },
70
- {
71
- key: "c",
72
- hideOnMobile: true,
73
- header: "Count",
74
- align: "right",
75
- cell: (s) => count(s.count),
76
- },
77
- {
78
- key: "b",
79
- hideOnMobile: true,
80
- header: "Bytes",
81
- align: "right",
82
- cell: (s) => bytes(s.bytes),
83
- },
84
- {
85
- key: "avg",
86
- hideOnMobile: true,
87
- header: "Avg",
88
- align: "right",
89
- cell: (s) => ms(s.avg, 3),
90
- },
91
- ]}
92
- />
93
- <h2 className="text-sm font-semibold">Recent</h2>
94
- <DataTable
95
- rows={p.recent}
96
- rowKey={(r) => r.id}
97
- empty={
98
- <EmptyState
99
- icon={Cable}
100
- title="No broadcasts in this window"
101
- description="Action Cable broadcasts appear here as your app sends them."
102
- />
103
- }
104
- keyboardNav={{
105
- onOpen: (r, opts) => {
106
- const href = recentHref(r)
107
- if (!href) return
108
- if (opts?.newTab) window.open(href, "_blank")
109
- else router.visit(href)
110
- },
111
- }}
112
- columns={[
113
- {
114
- key: "when",
115
- header: "When",
116
- cell: (r) => <span className="text-xs">{when(r.occurred_at)}</span>,
117
- },
118
- {
119
- key: "k",
120
- header: "Kind",
121
- cell: (r) =>
122
- r.failed ? (
123
- <span className="flex items-center gap-1.5">
124
- {r.kind}
125
- <Badge variant="destructive">failed</Badge>
126
- </span>
127
- ) : (
128
- r.kind
129
- ),
130
- },
131
- {
132
- key: "s",
133
- header: "Stream / channel",
134
- cell: (r) => (
135
- <span className="font-mono text-xs">
136
- {r.stream ?? r.channel}
137
- {r.action ? `#${r.action}` : ""}
138
- </span>
139
- ),
140
- },
141
- {
142
- key: "b",
143
- hideOnMobile: true,
144
- header: "Bytes",
145
- align: "right",
146
- cell: (r) => bytes(r.bytes),
147
- },
148
- {
149
- key: "d",
150
- header: "Duration",
151
- align: "right",
152
- cell: (r) => ms(r.duration, 3),
153
- },
154
- {
155
- key: "in",
156
- hideOnMobile: true,
157
- header: "In",
158
- cell: (r) => {
159
- const href = recentHref(r)
160
- return href ? (
161
- <Link className="font-mono text-xs hover:underline" href={href}>
162
- {r.execution_preview}
163
- </Link>
164
- ) : null
165
- },
166
- },
167
- ]}
168
- />
169
- </EnvLayout>
170
- )
171
- }
@@ -1,127 +0,0 @@
1
- import { router, usePage } from "@inertiajs/react"
2
- import { Layers } from "lucide-react"
3
-
4
- import { VolumePanel } from "@/components/railwatch/chart-panel"
5
- import { DataTable } from "@/components/railwatch/data-table"
6
- import { EmptyState } from "@/components/railwatch/empty-state"
7
- import { FilterBar } from "@/components/railwatch/filter-bar"
8
- import { PageHeader } from "@/components/railwatch/page-header"
9
- import { SparklineCell } from "@/components/railwatch/sparkline-cell"
10
- import EnvLayout from "@/layouts/env-layout"
11
- import { count, ms } from "@/lib/format"
12
- import * as R from "@/routes"
13
- import type { SeriesPoint, SharedProps } from "@/types"
14
-
15
- interface Key {
16
- group_hash: string
17
- key: string
18
- count: number
19
- hits: number
20
- misses: number
21
- hit_rate: number | null
22
- avg: number
23
- sparkline: number[]
24
- }
25
- interface Props {
26
- keys: Key[]
27
- series: SeriesPoint[]
28
- q: string
29
- }
30
-
31
- export default function CacheEvents(p: Props) {
32
- const { environment, window } = usePage<SharedProps>().props
33
- const a = environment!.application_id
34
- const e = environment!.id
35
- return (
36
- <EnvLayout title="Cache">
37
- <PageHeader
38
- title="Cache"
39
- description="Rails.cache reads, writes, and hit rates by key shape (ids collapsed)."
40
- />
41
- <VolumePanel
42
- legend="cache"
43
- label="Cache ops"
44
- seriesLabel="Cache ops"
45
- data={p.series}
46
- />
47
- <FilterBar
48
- value={p.q}
49
- fields={[{ key: "store", label: "Store" }]}
50
- onChange={(q) =>
51
- router.visit(
52
- R.applicationEnvironmentCacheEventsPath(a, e, {
53
- window,
54
- q: q || undefined,
55
- }),
56
- { preserveState: true },
57
- )
58
- }
59
- placeholder="store:MemoryStore"
60
- />
61
- <DataTable
62
- rows={p.keys}
63
- rowKey={(k) => k.group_hash}
64
- empty={
65
- <EmptyState
66
- icon={Layers}
67
- title="No cache activity in this window"
68
- description="Cache reads and writes instrumented by the gem appear here."
69
- />
70
- }
71
- columns={[
72
- {
73
- key: "k",
74
- header: "Store · key",
75
- cell: (k) => <span className="font-mono text-xs">{k.key}</span>,
76
- },
77
- {
78
- key: "trend",
79
- hideOnMobile: true,
80
- header: "Trend",
81
- cell: (k) => <SparklineCell data={k.sparkline} />,
82
- },
83
- {
84
- key: "n",
85
- header: "Ops",
86
- align: "right",
87
- cell: (k) => count(k.count),
88
- },
89
- {
90
- key: "h",
91
- header: "Hits",
92
- align: "right",
93
- cell: (k) => count(k.hits),
94
- },
95
- {
96
- key: "m",
97
- hideOnMobile: true,
98
- header: "Misses",
99
- align: "right",
100
- cell: (k) => count(k.misses),
101
- },
102
- {
103
- key: "r",
104
- hideOnMobile: true,
105
- header: "Hit rate",
106
- align: "right",
107
- cell: (k) =>
108
- k.hit_rate == null ? (
109
- "–"
110
- ) : (
111
- <span className={k.hit_rate < 50 ? "text-amber-600" : ""}>
112
- {k.hit_rate}%
113
- </span>
114
- ),
115
- },
116
- {
117
- key: "avg",
118
- hideOnMobile: true,
119
- header: "Avg",
120
- align: "right",
121
- cell: (k) => ms(k.avg, 3),
122
- },
123
- ]}
124
- />
125
- </EnvLayout>
126
- )
127
- }