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,170 +0,0 @@
1
- import type { ReactNode } from "react"
2
-
3
- import {
4
- LatencyChart,
5
- ThroughputChart,
6
- } from "@/components/railwatch/series-chart"
7
- import { count, ms } from "@/lib/format"
8
- import { cn } from "@/lib/utils"
9
- import type { DeployMarker, Percentile, SeriesPoint } from "@/types"
10
-
11
- // Nightwatch's chart card: a mono label with the headline number at the
12
- // left, a colour-keyed legend with per-series totals at the right, the
13
- // chart below, and the bucket range under it (drawn by the chart itself).
14
- export function ChartPanel({
15
- label,
16
- value,
17
- legend,
18
- children,
19
- className,
20
- }: {
21
- label: string
22
- value: ReactNode
23
- legend?: { key: string; label: string; value: ReactNode; color: string }[]
24
- children: ReactNode
25
- className?: string
26
- }) {
27
- return (
28
- <div className={cn("bg-card rounded-lg border p-4", className)}>
29
- <div className="mb-3 flex items-start justify-between gap-4">
30
- <div className="min-w-0">
31
- <div className="label-caps">{label}</div>
32
- <div className="mt-0.5 text-xl font-semibold tracking-tight tabular-nums">
33
- {value}
34
- </div>
35
- </div>
36
- {legend && (
37
- <div className="flex shrink-0 gap-4">
38
- {legend.map((l) => (
39
- <div key={l.key} className="text-right">
40
- <div className="label-caps flex items-center justify-end gap-1.5">
41
- <span
42
- className="inline-block h-2.5 w-1 rounded-sm"
43
- style={{ background: l.color }}
44
- />
45
- {l.label}
46
- </div>
47
- <div className="mt-0.5 font-mono text-sm tabular-nums">
48
- {l.value}
49
- </div>
50
- </div>
51
- ))}
52
- </div>
53
- )}
54
- </div>
55
- {children}
56
- </div>
57
- )
58
- }
59
-
60
- function sum(data: SeriesPoint[], key: "count" | "errors" | "client_errors") {
61
- return data.reduce((n, d) => n + d[key], 0)
62
- }
63
-
64
- // What the three stacked series mean for each record type. Rollups store
65
- // them as count / client_errors / errors; the words differ per page.
66
- export const VOLUME_LEGENDS = {
67
- request: { ok: "1/2/3xx", warn: "4xx", error: "5xx" },
68
- job: { ok: "processed", warn: "released", error: "failed" },
69
- outcome: { ok: "ok", warn: null, error: "failed" },
70
- exception: { ok: "handled", warn: null, error: "unhandled" },
71
- cache: { ok: "hit", warn: "miss", error: "write" },
72
- } as const
73
-
74
- export type VolumeLegend = keyof typeof VOLUME_LEGENDS
75
-
76
- // Volume panel: total as the headline, and a colour-keyed legend naming the
77
- // ok / warning / error slices in the record type's own words.
78
- export function VolumePanel({
79
- data,
80
- deploys,
81
- label = "Requests",
82
- seriesLabel,
83
- legend = "request",
84
- className,
85
- }: {
86
- data: SeriesPoint[]
87
- deploys?: DeployMarker[]
88
- label?: string
89
- seriesLabel?: string
90
- legend?: VolumeLegend
91
- className?: string
92
- }) {
93
- const total = sum(data, "count")
94
- const errors = sum(data, "errors")
95
- const client = sum(data, "client_errors")
96
- const words = VOLUME_LEGENDS[legend]
97
- const items = [
98
- {
99
- key: "ok",
100
- label: words.ok,
101
- value: count(total - errors - client),
102
- color: "var(--ok)",
103
- },
104
- words.warn && {
105
- key: "warn",
106
- label: words.warn,
107
- value: count(client),
108
- color: "var(--warning)",
109
- },
110
- {
111
- key: "error",
112
- label: words.error,
113
- value: count(errors),
114
- color: "var(--danger)",
115
- },
116
- ].filter((i): i is NonNullable<typeof i> => Boolean(i))
117
- return (
118
- <ChartPanel
119
- label={label}
120
- value={count(total)}
121
- className={className}
122
- legend={items}
123
- >
124
- <ThroughputChart data={data} deploys={deploys} label={seriesLabel} />
125
- </ChartPanel>
126
- )
127
- }
128
-
129
- // Duration panel: min — max range as the headline, avg and pNN legend.
130
- export function DurationPanel({
131
- data,
132
- deploys,
133
- percentile = "p95",
134
- label = "Duration",
135
- className,
136
- }: {
137
- data: SeriesPoint[]
138
- deploys?: DeployMarker[]
139
- percentile?: Percentile
140
- label?: string
141
- className?: string
142
- }) {
143
- const withData = data.filter((d) => d.count > 0)
144
- const avgs = withData.flatMap((d) => (d.avg == null ? [] : [d.avg]))
145
- const pcts = withData.flatMap((d) =>
146
- d[percentile] == null ? [] : [d[percentile]],
147
- )
148
- const avg = avgs.length ? avgs.reduce((a, b) => a + b, 0) / avgs.length : 0
149
- const pct = pcts.length ? Math.max(...pcts) : 0
150
- const lo = avgs.length ? Math.min(...avgs) : 0
151
- const hi = pcts.length ? Math.max(...pcts) : 0
152
- return (
153
- <ChartPanel
154
- label={label}
155
- value={withData.length ? `${ms(lo)} — ${ms(hi)}` : "—"}
156
- className={className}
157
- legend={[
158
- { key: "avg", label: "avg", value: ms(avg), color: "var(--warning)" },
159
- {
160
- key: percentile,
161
- label: percentile,
162
- value: ms(pct),
163
- color: "var(--primary)",
164
- },
165
- ]}
166
- >
167
- <LatencyChart data={data} deploys={deploys} percentile={percentile} />
168
- </ChartPanel>
169
- )
170
- }
@@ -1,28 +0,0 @@
1
- import { cn } from "@/lib/utils"
2
-
3
- export function Sql({
4
- children,
5
- className,
6
- }: {
7
- children: string
8
- className?: string
9
- }) {
10
- return (
11
- <code
12
- className={cn("block max-w-full truncate font-mono text-xs", className)}
13
- title={children}
14
- >
15
- {children}
16
- </code>
17
- )
18
- }
19
-
20
- export function Mono({
21
- children,
22
- className,
23
- }: {
24
- children: React.ReactNode
25
- className?: string
26
- }) {
27
- return <span className={cn("font-mono text-xs", className)}>{children}</span>
28
- }
@@ -1,132 +0,0 @@
1
- import { router, usePage } from "@inertiajs/react"
2
- import { AlertOctagon, LayoutGrid, Plug, SearchIcon, Users } from "lucide-react"
3
- import { useState } from "react"
4
-
5
- import {
6
- CommandDialog,
7
- CommandEmpty,
8
- CommandGroup,
9
- CommandInput,
10
- CommandItem,
11
- CommandList,
12
- CommandSeparator,
13
- } from "@/components/ui/command"
14
- import { envPages } from "@/lib/env-pages"
15
- import {
16
- applicationEnvironmentOverviewPath,
17
- dashboardPath,
18
- issuesPath,
19
- settingsIntegrationsPath,
20
- settingsMembersPath,
21
- } from "@/routes"
22
- import type { NavItem, SharedProps } from "@/types"
23
-
24
- const staticNav = (embedded: boolean): NavItem[] => [
25
- ...(embedded
26
- ? []
27
- : [{ title: "Dashboard", href: dashboardPath(), icon: LayoutGrid }]),
28
- { title: "Issues", href: issuesPath(), icon: AlertOctagon },
29
- ...(embedded
30
- ? []
31
- : [
32
- { title: "Integrations", href: settingsIntegrationsPath(), icon: Plug },
33
- { title: "Members", href: settingsMembersPath(), icon: Users },
34
- ]),
35
- ]
36
-
37
- export function CommandPalette({
38
- open,
39
- onOpenChange,
40
- }: {
41
- open: boolean
42
- onOpenChange: (open: boolean) => void
43
- }) {
44
- const [query, setQuery] = useState("")
45
- const {
46
- applications,
47
- environment,
48
- embedded,
49
- window: currentWindow,
50
- } = usePage<SharedProps>().props
51
-
52
- const go = (href: string) => {
53
- onOpenChange(false)
54
- router.visit(href)
55
- }
56
-
57
- const envItems = environment
58
- ? envPages(
59
- environment.application_id,
60
- environment.id,
61
- currentWindow ?? "24h",
62
- )
63
- : []
64
-
65
- return (
66
- <CommandDialog open={open} onOpenChange={onOpenChange}>
67
- <CommandInput
68
- placeholder="Search pages, applications, issues…"
69
- value={query}
70
- onValueChange={setQuery}
71
- />
72
- <CommandList>
73
- <CommandEmpty>No results found.</CommandEmpty>
74
- <CommandGroup heading="Navigation">
75
- {staticNav(!!embedded).map((item) => (
76
- <CommandItem key={item.title} onSelect={() => go(item.href)}>
77
- {item.icon && <item.icon />}
78
- <span>{item.title}</span>
79
- </CommandItem>
80
- ))}
81
- </CommandGroup>
82
- {envItems.length > 0 && (
83
- <CommandGroup
84
- heading={`${environment!.application_name} · ${environment!.name}`}
85
- >
86
- {envItems.map((item) => (
87
- <CommandItem key={item.title} onSelect={() => go(item.href)}>
88
- {item.icon && <item.icon />}
89
- <span>{item.title}</span>
90
- </CommandItem>
91
- ))}
92
- </CommandGroup>
93
- )}
94
- {!embedded && (
95
- <CommandGroup heading="Applications">
96
- {applications.flatMap((app) =>
97
- app.environments.map((env) => (
98
- <CommandItem
99
- key={`${app.id}-${env.id}`}
100
- value={`${app.name} ${env.name}`}
101
- onSelect={() =>
102
- go(applicationEnvironmentOverviewPath(app.id, env.id))
103
- }
104
- >
105
- <span>
106
- {app.name} · {env.name}
107
- </span>
108
- </CommandItem>
109
- )),
110
- )}
111
- </CommandGroup>
112
- )}
113
- {query.trim() && (
114
- <>
115
- <CommandSeparator />
116
- <CommandGroup heading="Search">
117
- <CommandItem
118
- value={query}
119
- onSelect={() =>
120
- go(`${issuesPath()}?q=${encodeURIComponent(query)}`)
121
- }
122
- >
123
- <SearchIcon />
124
- <span>Search issues for &ldquo;{query}&rdquo;</span>
125
- </CommandItem>
126
- </CommandGroup>
127
- </>
128
- )}
129
- </CommandList>
130
- </CommandDialog>
131
- )
132
- }
@@ -1,52 +0,0 @@
1
- import { CheckIcon, CopyIcon } from "lucide-react"
2
- import { useState } from "react"
3
-
4
- import { Button } from "@/components/ui/button"
5
- import { useClipboard } from "@/hooks/use-clipboard"
6
- import { cn } from "@/lib/utils"
7
-
8
- const COPIED_RESET_MS = 1500
9
-
10
- // A code block with a copy button in its corner. Everything Railwatch asks a
11
- // developer to paste somewhere else — a Gemfile line, a token, an MCP client
12
- // config — goes through this, so "copy" behaves the same everywhere.
13
- export function CopyBlock({
14
- code,
15
- className,
16
- }: {
17
- code: string
18
- className?: string
19
- }) {
20
- const [, copy] = useClipboard()
21
- const [justCopied, setJustCopied] = useState(false)
22
-
23
- const handleCopy = () => {
24
- void copy(code).then((ok) => {
25
- if (!ok) return
26
- setJustCopied(true)
27
- window.setTimeout(() => setJustCopied(false), COPIED_RESET_MS)
28
- })
29
- }
30
-
31
- return (
32
- <div className={cn("relative", className)}>
33
- <pre className="bg-muted overflow-x-auto rounded p-3 pr-10 font-mono text-xs">
34
- {code}
35
- </pre>
36
- <Button
37
- type="button"
38
- aria-label={justCopied ? "Copied code" : "Copy code"}
39
- variant="ghost"
40
- size="icon"
41
- className="absolute top-1.5 right-1.5 size-6"
42
- onClick={handleCopy}
43
- >
44
- {justCopied ? (
45
- <CheckIcon className="size-3.5 text-emerald-500" />
46
- ) : (
47
- <CopyIcon className="size-3.5 opacity-60" />
48
- )}
49
- </Button>
50
- </div>
51
- )
52
- }
@@ -1,49 +0,0 @@
1
- import { CheckIcon, CopyIcon } from "lucide-react"
2
- import { useState } from "react"
3
-
4
- import { Button } from "@/components/ui/button"
5
- import { useClipboard } from "@/hooks/use-clipboard"
6
- import { cn } from "@/lib/utils"
7
-
8
- const COPIED_RESET_MS = 1500
9
-
10
- export function CopyId({
11
- value,
12
- label,
13
- className,
14
- }: {
15
- value: string
16
- label?: string
17
- className?: string
18
- }) {
19
- const [, copy] = useClipboard()
20
- const [justCopied, setJustCopied] = useState(false)
21
-
22
- const handleCopy = () => {
23
- void copy(value).then((ok) => {
24
- if (!ok) return
25
- setJustCopied(true)
26
- window.setTimeout(() => setJustCopied(false), COPIED_RESET_MS)
27
- })
28
- }
29
-
30
- return (
31
- <Button
32
- type="button"
33
- variant="ghost"
34
- size="sm"
35
- className={cn(
36
- "h-auto gap-1.5 px-1.5 py-0.5 font-mono text-xs",
37
- className,
38
- )}
39
- onClick={handleCopy}
40
- >
41
- {label ?? value}
42
- {justCopied ? (
43
- <CheckIcon className="size-3 text-emerald-500" />
44
- ) : (
45
- <CopyIcon className="size-3 opacity-60" />
46
- )}
47
- </Button>
48
- )
49
- }
@@ -1,39 +0,0 @@
1
- import { router } from "@inertiajs/react"
2
- import { useState } from "react"
3
-
4
- import { Button } from "@/components/ui/button"
5
- import type { CursorMeta } from "@/types"
6
-
7
- export function CursorLoadMore({
8
- meta,
9
- href,
10
- only,
11
- }: {
12
- meta: CursorMeta
13
- href: (cursor: string) => string
14
- only: string[]
15
- }) {
16
- const [loading, setLoading] = useState(false)
17
- if (!meta.has_more || !meta.next_cursor) return null
18
-
19
- return (
20
- <div className="flex justify-center pt-3">
21
- <Button
22
- type="button"
23
- variant="outline"
24
- disabled={loading}
25
- onClick={() =>
26
- router.visit(href(meta.next_cursor!), {
27
- only,
28
- preserveState: true,
29
- preserveScroll: true,
30
- onStart: () => setLoading(true),
31
- onFinish: () => setLoading(false),
32
- })
33
- }
34
- >
35
- {loading ? "Loading…" : "Load " + meta.limit + " more"}
36
- </Button>
37
- </div>
38
- )
39
- }
@@ -1,195 +0,0 @@
1
- import type { ReactNode } from "react"
2
- import { useEffect, useRef } from "react"
3
-
4
- import { useChartHover } from "@/components/railwatch/chart-hover"
5
- import { EmptyState } from "@/components/railwatch/empty-state"
6
- import {
7
- Table,
8
- TableBody,
9
- TableCell,
10
- TableHead,
11
- TableHeader,
12
- TableRow,
13
- } from "@/components/ui/table"
14
- import { useRowNav } from "@/hooks/use-row-nav"
15
- import { cn } from "@/lib/utils"
16
-
17
- const INTERACTIVE_TARGET =
18
- 'a, button, input, select, textarea, [role="button"], [role="link"], [contenteditable="true"]'
19
-
20
- function isInteractiveTarget(target: EventTarget | null) {
21
- return target instanceof Element && target.closest(INTERACTIVE_TARGET)
22
- }
23
-
24
- export interface Column<T> {
25
- key: string
26
- header: ReactNode
27
- cell: (row: T) => ReactNode
28
- className?: string
29
- align?: "left" | "right"
30
- // Hide this column below the md breakpoint. Phones get the columns that
31
- // identify the row and the one number that matters; the rest come back
32
- // on wider screens. Every page marks its own secondary columns.
33
- hideOnMobile?: boolean
34
- // The column that identifies the row (route, SQL, job class). It takes
35
- // whatever width the numeric columns leave over and truncates, so the
36
- // table fits its container instead of forcing a sideways scroll.
37
- grow?: boolean
38
- }
39
-
40
- export interface KeyboardNav<T> {
41
- onOpen: (row: T, opts?: { newTab?: boolean }) => void
42
- enabled?: boolean
43
- // Fires whenever j/k/Esc change the highlighted row, so a page can drive
44
- // other row-scoped shortcuts (e.g. issues' r/i/a) off the same state.
45
- onHighlightChange?: (row: T | null) => void
46
- }
47
-
48
- export function DataTable<T>({
49
- rows,
50
- columns,
51
- rowKey,
52
- empty = "Nothing here yet.",
53
- onRowClick,
54
- keyboardNav,
55
- hoverKey,
56
- rowClassName,
57
- className,
58
- }: {
59
- rows: T[]
60
- columns: Column<T>[]
61
- rowKey: (row: T) => string | number
62
- empty?: ReactNode
63
- onRowClick?: (row: T) => void
64
- keyboardNav?: KeyboardNav<T>
65
- // Per-row tint for a row whose own state matters (a crashed session), on
66
- // top of the highlight/hover states below.
67
- rowClassName?: (row: T) => string | undefined
68
- // Links rows to a chart via ChartHoverProvider: hovering a row sets this
69
- // key (e.g. the row's group_hash) as the shared hovered key, and rows
70
- // whose key matches an externally-hovered chart point get `data-hovered`.
71
- hoverKey?: (row: T) => string | undefined
72
- className?: string
73
- }) {
74
- const { highlighted, activate } = useRowNav({
75
- rows,
76
- rowKey,
77
- onOpen: keyboardNav?.onOpen ?? (() => undefined),
78
- enabled: Boolean(keyboardNav) && keyboardNav?.enabled !== false,
79
- })
80
-
81
- const rowRefs = useRef(new Map<string | number, HTMLTableRowElement>())
82
-
83
- useEffect(() => {
84
- if (highlighted == null) return
85
- rowRefs.current.get(highlighted)?.scrollIntoView({ block: "nearest" })
86
- }, [highlighted])
87
-
88
- useEffect(() => {
89
- if (!keyboardNav?.onHighlightChange) return
90
- const row = rows.find((r) => rowKey(r) === highlighted) ?? null
91
- keyboardNav.onHighlightChange(row)
92
- // eslint-disable-next-line react-hooks/exhaustive-deps
93
- }, [highlighted])
94
-
95
- const { hoveredKey, setHoveredKey } = useChartHover()
96
- const colClass = (c: Column<T>) =>
97
- cn(
98
- c.align === "right" && "text-right",
99
- c.hideOnMobile && "hidden md:table-cell",
100
- c.grow && "w-full max-w-0 truncate",
101
- c.className,
102
- )
103
-
104
- return (
105
- <div
106
- tabIndex={keyboardNav && keyboardNav.enabled !== false ? 0 : undefined}
107
- onFocusCapture={activate}
108
- onPointerDownCapture={activate}
109
- className={cn(
110
- "bg-card -mx-3 overflow-x-auto border-y md:mx-0 md:rounded-lg md:border",
111
- className,
112
- )}
113
- >
114
- <Table>
115
- <TableHeader>
116
- <TableRow className="hover:bg-transparent">
117
- {columns.map((c) => (
118
- <TableHead key={c.key} className={colClass(c)}>
119
- {c.header}
120
- </TableHead>
121
- ))}
122
- </TableRow>
123
- </TableHeader>
124
- <TableBody>
125
- {rows.length === 0 && (
126
- <TableRow>
127
- <TableCell
128
- colSpan={columns.length}
129
- className="p-0 whitespace-normal"
130
- >
131
- {typeof empty === "string" ? (
132
- <EmptyState title={empty} />
133
- ) : (
134
- <div className="text-muted-foreground min-h-24 text-center">
135
- {empty}
136
- </div>
137
- )}
138
- </TableCell>
139
- </TableRow>
140
- )}
141
- {rows.map((row) => {
142
- const key = rowKey(row)
143
- const isHighlighted = Boolean(keyboardNav) && highlighted === key
144
- const rowHoverKey = hoverKey?.(row)
145
- const isHovered = rowHoverKey != null && hoveredKey === rowHoverKey
146
- return (
147
- <TableRow
148
- key={key}
149
- ref={(el) => {
150
- if (el) rowRefs.current.set(key, el)
151
- else rowRefs.current.delete(key)
152
- }}
153
- data-state={isHighlighted ? "highlighted" : undefined}
154
- data-hovered={isHovered || undefined}
155
- className={cn(
156
- onRowClick && "cursor-pointer",
157
- rowClassName?.(row),
158
- isHighlighted &&
159
- "bg-accent shadow-[inset_2px_0_0_0_var(--primary)]",
160
- isHovered && !isHighlighted && "bg-accent/50",
161
- )}
162
- onClick={
163
- onRowClick
164
- ? (event) => {
165
- if (!isInteractiveTarget(event.target)) onRowClick(row)
166
- }
167
- : undefined
168
- }
169
- onMouseEnter={
170
- rowHoverKey ? () => setHoveredKey(rowHoverKey) : undefined
171
- }
172
- onMouseLeave={
173
- rowHoverKey ? () => setHoveredKey(null) : undefined
174
- }
175
- >
176
- {columns.map((c) => (
177
- <TableCell
178
- key={c.key}
179
- className={cn(
180
- "py-1.5 text-xs",
181
- c.align === "right" && "font-mono tabular-nums",
182
- colClass(c),
183
- )}
184
- >
185
- {c.cell(row)}
186
- </TableCell>
187
- ))}
188
- </TableRow>
189
- )
190
- })}
191
- </TableBody>
192
- </Table>
193
- </div>
194
- )
195
- }