railwatch 0.9.3 → 1.0.0

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