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,303 +0,0 @@
1
- import { Link, router, usePage } from "@inertiajs/react"
2
- import { FileText } from "lucide-react"
3
- import { useState } from "react"
4
-
5
- import { CursorLoadMore } from "@/components/railwatch/cursor-load-more"
6
- import { DataTable } from "@/components/railwatch/data-table"
7
- import { EmptyState } from "@/components/railwatch/empty-state"
8
- import { FilterBar } from "@/components/railwatch/filter-bar"
9
- import { PageHeader } from "@/components/railwatch/page-header"
10
- import { SavedViewsMenu } from "@/components/railwatch/saved-views"
11
- import { Segmented, SegmentedItem } from "@/components/railwatch/segmented"
12
- import { LevelBadge } from "@/components/railwatch/status-badge"
13
- import EnvLayout from "@/layouts/env-layout"
14
- import { parseFilter, serializeFilter } from "@/lib/filter"
15
- import { count, when } from "@/lib/format"
16
- import { cn } from "@/lib/utils"
17
- import * as R from "@/routes"
18
- import type { CursorMeta, SharedProps } from "@/types"
19
-
20
- interface LogRow {
21
- id: number
22
- level: string
23
- message: string
24
- tags: string[]
25
- occurred_at: string
26
- execution_id: string | null
27
- execution_source: string | null
28
- execution_preview: string | null
29
- source: string | null
30
- tenant: string | null
31
- user_ref: string | null
32
- context: string | null
33
- }
34
- interface Props {
35
- logs: LogRow[]
36
- counts: Record<string, number>
37
- // id -> the matching part of the message, each hit wrapped in MARK. Only
38
- // present for rows the full-text search actually matched.
39
- highlights: Record<number, string>
40
- pagination: CursorMeta
41
- q: string
42
- }
43
-
44
- // Telemetry::Log::SNIPPET_MARK: one character opens and closes every hit, so
45
- // the odd-numbered pieces of the split are the ones to mark.
46
- const MARK = "\u001f"
47
-
48
- // Long lines (structured events, stack dumps) are clamped to a few rows so
49
- // the table stays scannable, especially on phones where the message column
50
- // is narrow. Tapping the message toggles the full text. Short lines have
51
- // nothing to expand, so they are plain.
52
- const CLAMP_AT = 160
53
-
54
- function LogMessage({
55
- message,
56
- highlight,
57
- tags,
58
- }: {
59
- message: string
60
- highlight?: string
61
- tags: string[]
62
- }) {
63
- const [open, setOpen] = useState(false)
64
- const long = message.length > CLAMP_AT || message.includes("\n")
65
- const body = (
66
- <span
67
- className={cn(
68
- "font-mono text-xs break-all whitespace-pre-wrap",
69
- long && !open && "line-clamp-3",
70
- )}
71
- >
72
- {highlight ? <Snippet text={highlight} /> : message}
73
- {tags.length > 0 && (
74
- <span className="text-muted-foreground"> [{tags.join(", ")}]</span>
75
- )}
76
- </span>
77
- )
78
- if (!long) return body
79
- return (
80
- <button
81
- type="button"
82
- onClick={() => setOpen((o) => !o)}
83
- aria-expanded={open}
84
- className="block w-full cursor-pointer text-left"
85
- title={open ? "Collapse" : "Expand"}
86
- >
87
- {body}
88
- </button>
89
- )
90
- }
91
-
92
- function Snippet({ text }: { text: string }) {
93
- return (
94
- <>
95
- {text.split(MARK).map((part, i) =>
96
- i % 2 === 1 ? (
97
- <mark key={i} className="fts-mark">
98
- {part}
99
- </mark>
100
- ) : (
101
- <span key={i}>{part}</span>
102
- ),
103
- )}
104
- </>
105
- )
106
- }
107
-
108
- export default function Logs(p: Props) {
109
- const { environment, window, range } = usePage<SharedProps>().props
110
- const a = environment!.application_id
111
- const e = environment!.id
112
- const timeParams =
113
- window === "custom"
114
- ? { from: range?.from, to: range?.to }
115
- : { window: window }
116
- const cursorTimeParams = range
117
- ? { from: range.from, to: range.to }
118
- : timeParams
119
- const visit = (q: string) =>
120
- router.visit(
121
- R.applicationEnvironmentLogsPath(a, e, {
122
- ...timeParams,
123
- q: q || undefined,
124
- }),
125
- {
126
- only: ["logs", "pagination", "highlights", "counts", "q"],
127
- preserveState: true,
128
- preserveScroll: true,
129
- reset: ["logs", "highlights"],
130
- },
131
- )
132
- const logsPath = (cursor?: string) =>
133
- R.applicationEnvironmentLogsPath(a, e, {
134
- ...cursorTimeParams,
135
- q: p.q || undefined,
136
- cursor,
137
- limit: p.pagination.limit,
138
- })
139
- const currentLevel = parseFilter(p.q).fields.level
140
- const setLevel = (level: string | undefined) => {
141
- const parsed = parseFilter(p.q)
142
- const fields = { ...parsed.fields }
143
- if (level) fields.level = level
144
- else delete fields.level
145
- visit(serializeFilter({ text: parsed.text, fields }))
146
- }
147
- return (
148
- <EnvLayout title="Logs">
149
- <PageHeader
150
- title="Logs"
151
- description="Rails.logger lines and Rails.event structured events, linked to the request or job that wrote them."
152
- actions={<SavedViewsMenu page="logs" />}
153
- />
154
- <div className="-mx-3 [scrollbar-width:none] overflow-x-auto px-3 md:mx-0 md:px-0 [&::-webkit-scrollbar]:hidden">
155
- <Segmented className="w-max">
156
- <SegmentedItem
157
- active={!currentLevel}
158
- onClick={() => setLevel(undefined)}
159
- >
160
- All
161
- </SegmentedItem>
162
- {["debug", "info", "warn", "error", "fatal", "event"].map((l) => (
163
- <SegmentedItem
164
- key={l}
165
- active={currentLevel === l}
166
- onClick={() => setLevel(l)}
167
- className="gap-1.5"
168
- >
169
- {l}
170
- <span className="opacity-60">{count(p.counts[l] ?? 0)}</span>
171
- </SegmentedItem>
172
- ))}
173
- </Segmented>
174
- </div>
175
- <FilterBar
176
- value={p.q}
177
- fields={[
178
- { key: "after", label: "After" },
179
- { key: "before", label: "Before" },
180
- { key: "user", label: "User" },
181
- { key: "deploy", label: "Deploy" },
182
- {
183
- key: "level",
184
- label: "Level",
185
- options: [
186
- "debug",
187
- "info",
188
- "warn",
189
- "error",
190
- "fatal",
191
- "event",
192
- "unknown",
193
- ],
194
- },
195
- {
196
- key: "kind",
197
- label: "Execution kind",
198
- options: ["request", "job", "scheduled_task", "command"],
199
- },
200
- { key: "source", label: "Source" },
201
- { key: "tenant", label: "Tenant" },
202
- ]}
203
- onChange={visit}
204
- placeholder="level:error kind:job source:app/jobs/sync_job.rb:12"
205
- />
206
- <div className="text-muted-foreground flex flex-wrap items-baseline gap-x-3 gap-y-1 text-xs">
207
- <span className="font-mono tabular-nums">
208
- {count(p.logs.length)} {p.logs.length === 1 ? "line" : "lines"}
209
- </span>
210
- <span>
211
- Full-text search: quotes for phrases, -word to exclude, prefixes match
212
- </span>
213
- </div>
214
- <DataTable
215
- rows={p.logs}
216
- rowKey={(l) => l.id}
217
- empty={
218
- <EmptyState
219
- icon={FileText}
220
- title="No log lines match"
221
- signal="caution"
222
- description="Log lines are captured from your app's Rails logger."
223
- />
224
- }
225
- columns={[
226
- {
227
- key: "when",
228
- header: "When",
229
- className: "w-40",
230
- cell: (l) => (
231
- <span className="text-xs tabular-nums">
232
- {when(l.occurred_at)}
233
- </span>
234
- ),
235
- },
236
- {
237
- key: "lvl",
238
- header: "Level",
239
- cell: (l) => <LevelBadge level={l.level} />,
240
- },
241
- {
242
- key: "msg",
243
- header: "Message",
244
- grow: true,
245
- className: "whitespace-normal",
246
- cell: (l) => (
247
- <LogMessage
248
- message={l.message}
249
- highlight={p.highlights[l.id]}
250
- tags={l.tags}
251
- />
252
- ),
253
- },
254
- {
255
- key: "in",
256
- hideOnMobile: true,
257
- header: "In",
258
- cell: (l) =>
259
- l.execution_id ? (
260
- <Link
261
- className="font-mono text-xs hover:underline"
262
- href={R.applicationEnvironmentRequestPath(
263
- a,
264
- e,
265
- l.execution_id,
266
- )}
267
- >
268
- {l.execution_preview}
269
- </Link>
270
- ) : null,
271
- },
272
- {
273
- key: "t",
274
- hideOnMobile: true,
275
- header: "Tenant",
276
- cell: (l) =>
277
- l.tenant ? (
278
- <button
279
- className="font-mono text-xs hover:underline"
280
- onClick={() => {
281
- const parsed = parseFilter(p.q)
282
- visit(
283
- serializeFilter({
284
- text: parsed.text,
285
- fields: { ...parsed.fields, tenant: l.tenant! },
286
- }),
287
- )
288
- }}
289
- >
290
- {l.tenant}
291
- </button>
292
- ) : null,
293
- },
294
- ]}
295
- />
296
- <CursorLoadMore
297
- meta={p.pagination}
298
- href={logsPath}
299
- only={["logs", "pagination", "highlights"]}
300
- />
301
- </EnvLayout>
302
- )
303
- }
@@ -1,218 +0,0 @@
1
- import { Link, router, usePage } from "@inertiajs/react"
2
- import { Mail } 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 { PercentilePicker } from "@/components/railwatch/percentile-picker"
10
- import { SparklineCell } from "@/components/railwatch/sparkline-cell"
11
- import { Badge } from "@/components/ui/badge"
12
- import { usePercentile } from "@/hooks/use-percentile"
13
- import EnvLayout from "@/layouts/env-layout"
14
- import { count, ms, when } from "@/lib/format"
15
- import * as R from "@/routes"
16
- import type { GroupRow, SeriesPoint, SharedProps } from "@/types"
17
-
18
- interface MailRow {
19
- id: number
20
- mailer: string
21
- subject: string
22
- to: number
23
- cc: number
24
- bcc: number
25
- attachments: number
26
- delivery_method: string | null
27
- duration: number
28
- failed: boolean
29
- occurred_at: string
30
- execution_id: string | null
31
- execution_preview: string | null
32
- }
33
- interface Props {
34
- mailers: GroupRow[]
35
- series: SeriesPoint[]
36
- recent: MailRow[]
37
- q: string
38
- }
39
-
40
- export default function Mails(p: Props) {
41
- const { environment, window } = usePage<SharedProps>().props
42
- const { percentile } = usePercentile()
43
- const a = environment!.application_id
44
- const e = environment!.id
45
- const mailHref = (m: MailRow) =>
46
- m.execution_id
47
- ? R.applicationEnvironmentRequestPath(a, e, m.execution_id)
48
- : null
49
- return (
50
- <EnvLayout title="Mail">
51
- <PageHeader
52
- title="Mail"
53
- description="Action Mailer deliveries: recipients, delivery method, render and send time."
54
- actions={<PercentilePicker />}
55
- />
56
- <VolumePanel
57
- legend="outcome"
58
- label="Deliveries"
59
- seriesLabel="Deliveries"
60
- data={p.series}
61
- />
62
- <DataTable
63
- rows={p.mailers}
64
- rowKey={(m) => m.group_hash}
65
- empty={
66
- <EmptyState
67
- icon={Mail}
68
- title="No mail sent in this window"
69
- description="ActionMailer deliveries appear here as your app sends them."
70
- />
71
- }
72
- columns={[
73
- {
74
- key: "m",
75
- header: "Mailer",
76
- cell: (m) => <span className="font-mono text-xs">{m.name}</span>,
77
- },
78
- {
79
- key: "trend",
80
- hideOnMobile: true,
81
- header: "Trend",
82
- cell: (m) => <SparklineCell data={m.sparkline} />,
83
- },
84
- {
85
- key: "n",
86
- header: "Sent",
87
- align: "right",
88
- cell: (m) => count(m.count),
89
- },
90
- {
91
- key: "f",
92
- hideOnMobile: true,
93
- header: "Failed",
94
- align: "right",
95
- cell: (m) => m.errors,
96
- },
97
- {
98
- key: "avg",
99
- hideOnMobile: true,
100
- header: "Avg",
101
- align: "right",
102
- cell: (m) => ms(m.avg),
103
- },
104
- {
105
- key: percentile,
106
- header: percentile,
107
- align: "right",
108
- cell: (m) => (
109
- <span className="font-semibold">{ms(m[percentile])}</span>
110
- ),
111
- },
112
- ]}
113
- />
114
- <h2 className="text-sm font-semibold">Recent</h2>
115
- <FilterBar
116
- value={p.q}
117
- fields={[
118
- { key: "mailer", label: "Mailer" },
119
- { key: "kind", label: "Kind" },
120
- ]}
121
- onChange={(q) =>
122
- router.visit(
123
- R.applicationEnvironmentMailsPath(a, e, {
124
- window,
125
- q: q || undefined,
126
- }),
127
- { preserveState: true },
128
- )
129
- }
130
- placeholder="mailer:WidgetMailer kind:smtp"
131
- />
132
- <DataTable
133
- rows={p.recent}
134
- rowKey={(m) => m.id}
135
- empty={
136
- <EmptyState
137
- icon={Mail}
138
- title="No mail sent in this window"
139
- description="ActionMailer deliveries appear here as your app sends them."
140
- />
141
- }
142
- keyboardNav={{
143
- onOpen: (m, opts) => {
144
- const href = mailHref(m)
145
- if (!href) return
146
- if (opts?.newTab) globalThis.window.open(href, "_blank")
147
- else router.visit(href)
148
- },
149
- }}
150
- columns={[
151
- {
152
- key: "when",
153
- header: "When",
154
- cell: (m) => <span className="text-xs">{when(m.occurred_at)}</span>,
155
- },
156
- {
157
- key: "m",
158
- header: "Mailer",
159
- cell: (m) => <span className="font-mono text-xs">{m.mailer}</span>,
160
- },
161
- {
162
- key: "s",
163
- header: "Subject",
164
- cell: (m) => (
165
- <span className="line-clamp-1 text-xs">{m.subject}</span>
166
- ),
167
- },
168
- {
169
- key: "to",
170
- hideOnMobile: true,
171
- header: "To/CC/BCC",
172
- align: "right",
173
- cell: (m) => `${m.to}/${m.cc}/${m.bcc}`,
174
- },
175
- {
176
- key: "att",
177
- hideOnMobile: true,
178
- header: "Att.",
179
- align: "right",
180
- cell: (m) => m.attachments,
181
- },
182
- {
183
- key: "dm",
184
- hideOnMobile: true,
185
- header: "Via",
186
- cell: (m) => m.delivery_method,
187
- },
188
- {
189
- key: "st",
190
- header: "",
191
- cell: (m) =>
192
- m.failed ? <Badge variant="destructive">failed</Badge> : null,
193
- },
194
- {
195
- key: "d",
196
- hideOnMobile: true,
197
- header: "Duration",
198
- align: "right",
199
- cell: (m) => ms(m.duration),
200
- },
201
- {
202
- key: "in",
203
- hideOnMobile: true,
204
- header: "In",
205
- cell: (m) => {
206
- const href = mailHref(m)
207
- return href ? (
208
- <Link className="font-mono text-xs hover:underline" href={href}>
209
- {m.execution_preview}
210
- </Link>
211
- ) : null
212
- },
213
- },
214
- ]}
215
- />
216
- </EnvLayout>
217
- )
218
- }
@@ -1,161 +0,0 @@
1
- import { Link, router, usePage } from "@inertiajs/react"
2
- import { BellRing } 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 EnvLayout from "@/layouts/env-layout"
8
- import { count, ms, when } from "@/lib/format"
9
- import * as R from "@/routes"
10
- import type { SharedProps } from "@/types"
11
-
12
- interface Group {
13
- group_hash: string
14
- name: string
15
- count: number
16
- errors: number
17
- avg: number
18
- p95: number
19
- }
20
- interface Recent {
21
- id: number
22
- notifier: string | null
23
- delivery_method: string | null
24
- duration: number
25
- failed: boolean
26
- occurred_at: string
27
- execution_id: string | null
28
- execution_preview: string | null
29
- }
30
- interface Props {
31
- groups: Group[]
32
- recent: Recent[]
33
- }
34
-
35
- export default function Notifications(p: Props) {
36
- const { environment } = usePage<SharedProps>().props
37
- const a = environment!.application_id
38
- const e = environment!.id
39
- const recentHref = (r: Recent) =>
40
- r.execution_id
41
- ? R.applicationEnvironmentRequestPath(a, e, r.execution_id)
42
- : null
43
- return (
44
- <EnvLayout title="Notifications">
45
- <PageHeader
46
- title="Notifications"
47
- description="ActionMailer/Noticed deliveries, grouped by notifier and delivery method."
48
- />
49
- <DataTable
50
- rows={p.groups}
51
- rowKey={(g) => g.group_hash}
52
- empty={
53
- <EmptyState
54
- icon={BellRing}
55
- title="No notifications in this window"
56
- description="Notifications delivered by your app appear here as they're sent."
57
- />
58
- }
59
- columns={[
60
- {
61
- key: "n",
62
- header: "Notifier",
63
- cell: (g) => <span className="font-mono text-xs">{g.name}</span>,
64
- },
65
- {
66
- key: "c",
67
- hideOnMobile: true,
68
- header: "Count",
69
- align: "right",
70
- cell: (g) => count(g.count),
71
- },
72
- {
73
- key: "f",
74
- hideOnMobile: true,
75
- header: "Failed",
76
- align: "right",
77
- cell: (g) => count(g.errors),
78
- },
79
- {
80
- key: "avg",
81
- hideOnMobile: true,
82
- header: "Avg",
83
- align: "right",
84
- cell: (g) => ms(g.avg, 3),
85
- },
86
- {
87
- key: "p95",
88
- header: "p95",
89
- align: "right",
90
- cell: (g) => ms(g.p95, 3),
91
- },
92
- ]}
93
- />
94
- <h2 className="text-sm font-semibold">Recent</h2>
95
- <DataTable
96
- rows={p.recent}
97
- rowKey={(r) => r.id}
98
- empty={
99
- <EmptyState
100
- icon={BellRing}
101
- title="No notifications in this window"
102
- description="Notifications delivered by your app appear here as they're sent."
103
- />
104
- }
105
- keyboardNav={{
106
- onOpen: (r, opts) => {
107
- const href = recentHref(r)
108
- if (!href) return
109
- if (opts?.newTab) window.open(href, "_blank")
110
- else router.visit(href)
111
- },
112
- }}
113
- columns={[
114
- {
115
- key: "when",
116
- header: "When",
117
- cell: (r) => <span className="text-xs">{when(r.occurred_at)}</span>,
118
- },
119
- {
120
- key: "n",
121
- header: "Notifier",
122
- cell: (r) => (
123
- <span className="font-mono text-xs">{r.notifier}</span>
124
- ),
125
- },
126
- {
127
- key: "dm",
128
- hideOnMobile: true,
129
- header: "Delivery method",
130
- cell: (r) => r.delivery_method,
131
- },
132
- {
133
- key: "f",
134
- hideOnMobile: true,
135
- header: "Failed",
136
- cell: (r) => (r.failed ? "yes" : ""),
137
- },
138
- {
139
- key: "d",
140
- header: "Duration",
141
- align: "right",
142
- cell: (r) => ms(r.duration, 3),
143
- },
144
- {
145
- key: "in",
146
- hideOnMobile: true,
147
- header: "In",
148
- cell: (r) => {
149
- const href = recentHref(r)
150
- return href ? (
151
- <Link className="font-mono text-xs hover:underline" href={href}>
152
- {r.execution_preview}
153
- </Link>
154
- ) : null
155
- },
156
- },
157
- ]}
158
- />
159
- </EnvLayout>
160
- )
161
- }