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,414 +0,0 @@
1
- import { Form, router, usePage } from "@inertiajs/react"
2
- import { Bell } from "lucide-react"
3
- import { useState } from "react"
4
-
5
- import InputError from "@/components/input-error"
6
- import { DataTable } from "@/components/railwatch/data-table"
7
- import { EmptyState } from "@/components/railwatch/empty-state"
8
- import { PageHeader } from "@/components/railwatch/page-header"
9
- import { RelativeTime } from "@/components/railwatch/relative-time"
10
- import { Button } from "@/components/ui/button"
11
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
12
- import { Input } from "@/components/ui/input"
13
- import { Label } from "@/components/ui/label"
14
- import EnvLayout from "@/layouts/env-layout"
15
- import * as R from "@/routes"
16
- import type { SharedProps } from "@/types"
17
-
18
- interface Threshold {
19
- id: number
20
- target_kind: string
21
- target: string
22
- metric: string
23
- limit: number
24
- window_minutes: number
25
- description: string
26
- }
27
- interface AnomalyRule {
28
- id: number
29
- target_kind: string
30
- target: string
31
- metric: string
32
- deviation: number
33
- window_minutes: number
34
- baseline_days: number
35
- enabled: boolean
36
- last_fired_at: string | null
37
- description: string
38
- }
39
- interface Props {
40
- thresholds: Threshold[]
41
- routes: string[]
42
- jobs: string[]
43
- kinds: string[]
44
- metrics: string[]
45
- metrics_for_kind: Record<string, string[]>
46
- units: Record<string, string>
47
- llm_models: string[]
48
- anomaly_rules: AnomalyRule[]
49
- anomaly_target_kinds: string[]
50
- anomaly_metrics: string[]
51
- anomaly_metrics_for_kind: Record<string, string[]>
52
- }
53
-
54
- export default function Thresholds(p: Props) {
55
- const { environment } = usePage<SharedProps>().props
56
- // Spend and tokens only exist on llm_calls, so the metric list narrows
57
- // with the kind rather than offering a rule the server would reject.
58
- const [kind, setKind] = useState(p.kinds[0])
59
- const [metric, setMetric] = useState(p.metrics[0])
60
- const [anomalyKind, setAnomalyKind] = useState(p.anomaly_target_kinds[0])
61
- const [anomalyMetric, setAnomalyMetric] = useState(p.anomaly_metrics[0])
62
- const anomalyAvailable =
63
- p.anomaly_metrics_for_kind[anomalyKind] ?? p.anomaly_metrics
64
- const available = p.metrics_for_kind[kind] ?? p.metrics
65
- const unit = p.units[metric] ?? "ms"
66
- const limitLabel =
67
- unit === "$"
68
- ? "Limit (US$)"
69
- : unit === "%"
70
- ? "Limit (%)"
71
- : unit === ""
72
- ? "Limit (count)"
73
- : "Limit (ms)"
74
- const a = environment!.application_id
75
- const e = environment!.id
76
- return (
77
- <EnvLayout title="Thresholds">
78
- <PageHeader
79
- withWindow={false}
80
- title="Thresholds"
81
- description="Performance rules. When a route, job, or query crosses one, Railwatch opens a performance issue and alerts the rules subscribed to thresholds."
82
- />
83
- <Card>
84
- <CardHeader>
85
- <CardTitle>Add a threshold</CardTitle>
86
- </CardHeader>
87
- <CardContent>
88
- <Form
89
- method="post"
90
- action={R.applicationEnvironmentThresholdsPath(a, e)}
91
- className="grid gap-3 md:grid-cols-6"
92
- >
93
- {({ errors, processing }) => (
94
- <>
95
- <div className="grid gap-1">
96
- <Label htmlFor="target_kind">Kind</Label>
97
- <select
98
- id="target_kind"
99
- name="target_kind"
100
- value={kind}
101
- onChange={(e) => {
102
- setKind(e.target.value)
103
- const next =
104
- p.metrics_for_kind[e.target.value] ?? p.metrics
105
- if (!next.includes(metric)) setMetric(next[0])
106
- }}
107
- className="border-input h-9 rounded-md border bg-transparent px-2 text-sm"
108
- >
109
- {p.kinds.map((k) => (
110
- <option key={k} value={k}>
111
- {k}
112
- </option>
113
- ))}
114
- </select>
115
- </div>
116
- <div className="grid gap-1 md:col-span-2">
117
- <Label htmlFor="target">Target</Label>
118
- <Input
119
- id="target"
120
- name="target"
121
- defaultValue="*"
122
- list="targets"
123
- placeholder="* for all, or a route / job name"
124
- />
125
- <datalist id="targets">
126
- <option value="*" />
127
- <option value="unmatched" />
128
- {p.routes.map((r) => (
129
- <option key={r} value={r} />
130
- ))}
131
- {p.jobs.map((j) => (
132
- <option key={j} value={j} />
133
- ))}
134
- {p.llm_models.map((m) => (
135
- <option key={m} value={m} />
136
- ))}
137
- </datalist>
138
- <InputError messages={errors.target} />
139
- </div>
140
- <div className="grid gap-1">
141
- <Label htmlFor="metric">Metric</Label>
142
- <select
143
- id="metric"
144
- name="metric"
145
- className="border-input h-9 rounded-md border bg-transparent px-2 text-sm"
146
- value={metric}
147
- onChange={(e) => setMetric(e.target.value)}
148
- >
149
- {available.map((m) => (
150
- <option key={m} value={m}>
151
- {m}
152
- </option>
153
- ))}
154
- </select>
155
- </div>
156
- <div className="grid gap-1">
157
- <Label htmlFor="limit">{limitLabel}</Label>
158
- <Input
159
- id="limit"
160
- name="limit"
161
- type="number"
162
- step="any"
163
- defaultValue={2000}
164
- />
165
- <InputError messages={errors.limit} />
166
- </div>
167
- <div className="grid gap-1">
168
- <Label htmlFor="window_minutes">Window (min)</Label>
169
- <Input
170
- id="window_minutes"
171
- name="window_minutes"
172
- type="number"
173
- defaultValue={5}
174
- />
175
- </div>
176
- <div className="md:col-span-6">
177
- <Button disabled={processing}>Add threshold</Button>
178
- </div>
179
- </>
180
- )}
181
- </Form>
182
- </CardContent>
183
- </Card>
184
- <DataTable
185
- rows={p.thresholds}
186
- rowKey={(t) => t.id}
187
- empty={
188
- <EmptyState
189
- icon={Bell}
190
- title="No thresholds yet"
191
- description="Start with requests * p95 2000ms to get alerted on regressions."
192
- />
193
- }
194
- columns={[
195
- {
196
- key: "d",
197
- header: "Rule",
198
- cell: (t) => <span className="text-sm">{t.description}</span>,
199
- },
200
- {
201
- key: "x",
202
- header: "",
203
- align: "right",
204
- cell: (t) => (
205
- <Button
206
- size="sm"
207
- variant="ghost"
208
- onClick={() =>
209
- router.delete(
210
- R.applicationEnvironmentThresholdPath(a, e, t.id),
211
- )
212
- }
213
- >
214
- Remove
215
- </Button>
216
- ),
217
- },
218
- ]}
219
- />
220
- <div>
221
- <h2 className="text-base font-semibold">Anomaly rules</h2>
222
- <p className="text-muted-foreground text-sm">
223
- A threshold needs a number you already know. An anomaly rule learns
224
- one: it compares the last few minutes against the same clock window on
225
- each of the previous days and opens an issue when today sits more than
226
- the given number of standard deviations above that baseline.
227
- </p>
228
- </div>
229
- <Card>
230
- <CardHeader>
231
- <CardTitle>Add an anomaly rule</CardTitle>
232
- </CardHeader>
233
- <CardContent>
234
- <Form
235
- method="post"
236
- action={R.applicationEnvironmentAnomalyRulesPath(a, e)}
237
- className="grid gap-3 md:grid-cols-6"
238
- >
239
- {({ errors, processing }) => (
240
- <>
241
- <div className="grid gap-1">
242
- <Label htmlFor="anomaly_target_kind">Kind</Label>
243
- <select
244
- id="anomaly_target_kind"
245
- name="target_kind"
246
- className="border-input h-9 rounded-md border bg-transparent px-2 text-sm"
247
- value={anomalyKind}
248
- onChange={(e) => {
249
- setAnomalyKind(e.target.value)
250
- const next =
251
- p.anomaly_metrics_for_kind[e.target.value] ??
252
- p.anomaly_metrics
253
- if (!next.includes(anomalyMetric))
254
- setAnomalyMetric(next[0])
255
- }}
256
- >
257
- {p.anomaly_target_kinds.map((k) => (
258
- <option key={k} value={k}>
259
- {k}
260
- </option>
261
- ))}
262
- </select>
263
- </div>
264
- <div className="grid gap-1 md:col-span-2">
265
- <Label htmlFor="anomaly_target">Target</Label>
266
- <Input
267
- id="anomaly_target"
268
- name="target"
269
- defaultValue="*"
270
- list="targets"
271
- placeholder="* for all, or a route / job name"
272
- />
273
- <InputError messages={errors.target} />
274
- </div>
275
- <div className="grid gap-1">
276
- <Label htmlFor="anomaly_metric">Metric</Label>
277
- <select
278
- id="anomaly_metric"
279
- name="metric"
280
- className="border-input h-9 rounded-md border bg-transparent px-2 text-sm"
281
- value={anomalyMetric}
282
- onChange={(e) => setAnomalyMetric(e.target.value)}
283
- >
284
- {anomalyAvailable.map((m) => (
285
- <option key={m} value={m}>
286
- {m}
287
- </option>
288
- ))}
289
- </select>
290
- </div>
291
- <div className="grid gap-1">
292
- <Label htmlFor="deviation">Deviation (σ)</Label>
293
- <Input
294
- id="deviation"
295
- name="deviation"
296
- type="number"
297
- step="0.5"
298
- min="0.5"
299
- defaultValue={3}
300
- />
301
- <InputError messages={errors.deviation} />
302
- </div>
303
- <div className="grid gap-1">
304
- <Label htmlFor="anomaly_window_minutes">Window (min)</Label>
305
- <Input
306
- id="anomaly_window_minutes"
307
- name="window_minutes"
308
- type="number"
309
- defaultValue={15}
310
- />
311
- </div>
312
- <div className="grid gap-1">
313
- <Label htmlFor="baseline_days">Baseline (days)</Label>
314
- <Input
315
- id="baseline_days"
316
- name="baseline_days"
317
- type="number"
318
- defaultValue={7}
319
- />
320
- <InputError messages={errors.baseline_days} />
321
- </div>
322
- <div className="grid gap-1">
323
- <Label htmlFor="enabled">Status</Label>
324
- <select
325
- id="enabled"
326
- name="enabled"
327
- className="border-input h-9 rounded-md border bg-transparent px-2 text-sm"
328
- >
329
- <option value="true">enabled</option>
330
- <option value="false">disabled</option>
331
- </select>
332
- </div>
333
- <div className="md:col-span-6">
334
- <Button disabled={processing}>Add anomaly rule</Button>
335
- </div>
336
- </>
337
- )}
338
- </Form>
339
- </CardContent>
340
- </Card>
341
- <DataTable
342
- rows={p.anomaly_rules}
343
- rowKey={(r) => r.id}
344
- empty={
345
- <EmptyState
346
- icon={Bell}
347
- title="No anomaly rules yet"
348
- description="Start with requests * p95 at 3σ over a 7-day baseline."
349
- />
350
- }
351
- columns={[
352
- {
353
- key: "d",
354
- header: "Rule",
355
- cell: (r) => (
356
- <span className="text-sm">
357
- {r.description}
358
- {r.enabled ? null : (
359
- <span className="text-muted-foreground label-caps ml-2">
360
- disabled
361
- </span>
362
- )}
363
- </span>
364
- ),
365
- },
366
- {
367
- key: "f",
368
- header: "Last fired",
369
- hideOnMobile: true,
370
- cell: (r) => (
371
- <RelativeTime
372
- iso={r.last_fired_at}
373
- className="text-muted-foreground font-mono text-xs"
374
- />
375
- ),
376
- },
377
- {
378
- key: "x",
379
- header: "",
380
- align: "right",
381
- cell: (r) => (
382
- <span className="flex justify-end gap-1">
383
- <Button
384
- size="sm"
385
- variant="ghost"
386
- onClick={() =>
387
- router.patch(
388
- R.applicationEnvironmentAnomalyRulePath(a, e, r.id),
389
- { enabled: !r.enabled },
390
- )
391
- }
392
- >
393
- {r.enabled ? "Disable" : "Enable"}
394
- </Button>
395
- <Button
396
- size="sm"
397
- variant="ghost"
398
- className="text-destructive"
399
- onClick={() =>
400
- router.delete(
401
- R.applicationEnvironmentAnomalyRulePath(a, e, r.id),
402
- )
403
- }
404
- >
405
- Remove
406
- </Button>
407
- </span>
408
- ),
409
- },
410
- ]}
411
- />
412
- </EnvLayout>
413
- )
414
- }
@@ -1,208 +0,0 @@
1
- import { Link, usePage } from "@inertiajs/react"
2
-
3
- import { CopyId } from "@/components/railwatch/copy-id"
4
- import { EmptyState } from "@/components/railwatch/empty-state"
5
- import { PageHeader } from "@/components/railwatch/page-header"
6
- import { Stat, StatStrip } from "@/components/railwatch/stat"
7
- import { KindBadge } from "@/components/railwatch/status-badge"
8
- import EnvLayout from "@/layouts/env-layout"
9
- import { executionPath } from "@/lib/execution-path"
10
- import { count, ms, statusTone } from "@/lib/format"
11
- import { cn } from "@/lib/utils"
12
- import type { SharedProps } from "@/types"
13
-
14
- interface Outgoing {
15
- id: number
16
- method: string | null
17
- url: string
18
- status_code: number | null
19
- duration: number | null
20
- offset: number
21
- }
22
- interface TraceExecution {
23
- execution_id: string
24
- kind: string
25
- name: string
26
- status: number | null
27
- outcome: string | null
28
- parent_id: string | null
29
- server: string | null
30
- occurred_at: string
31
- duration: number
32
- offset: number
33
- outgoing: Outgoing[]
34
- }
35
- interface Node {
36
- execution: TraceExecution
37
- children: Node[]
38
- }
39
- interface Props {
40
- trace_id: string
41
- roots: Node[]
42
- span_count: number
43
- services: string[]
44
- duration: number
45
- }
46
-
47
- interface Row {
48
- key: string
49
- depth: number
50
- kind: string
51
- label: string
52
- server: string | null
53
- offset: number
54
- duration: number
55
- href: string | null
56
- failed: boolean
57
- }
58
-
59
- const kindLabels: Record<string, string> = {
60
- request: "REQ",
61
- job_attempt: "JOB",
62
- scheduled_task: "TASK",
63
- command: "CMD",
64
- outgoing_request: "HTTP",
65
- }
66
-
67
- // Depth-first walk of the trace: each execution, then the HTTP calls it made
68
- // (one level in, and not clickable — the hop they led to is a row of its own
69
- // if that service reports to Railwatch), then its child executions.
70
- function flatten(nodes: Node[], a: number, e: number, depth = 0): Row[] {
71
- return nodes.flatMap((node) => {
72
- const x = node.execution
73
- return [
74
- {
75
- key: x.execution_id,
76
- depth,
77
- kind: x.kind,
78
- label: x.name,
79
- server: x.server,
80
- offset: x.offset,
81
- duration: x.duration,
82
- href: executionPath({
83
- applicationId: a,
84
- environmentId: e,
85
- source: x.kind,
86
- executionId: x.execution_id,
87
- })!,
88
- failed: statusTone(x.status, x.outcome) === "destructive",
89
- },
90
- ...x.outgoing.map((o) => ({
91
- key: `o-${o.id}`,
92
- depth: depth + 1,
93
- kind: "outgoing_request",
94
- label: `${o.method ?? ""} ${o.url}`.trim(),
95
- server: null,
96
- offset: o.offset,
97
- duration: o.duration ?? 0,
98
- href: null,
99
- failed: statusTone(o.status_code) === "destructive",
100
- })),
101
- ...flatten(node.children, a, e, depth + 1),
102
- ]
103
- })
104
- }
105
-
106
- // One waterfall row. Phones stack the bar under the name/duration line;
107
- // from md up the three parts sit on one line against a shared time scale.
108
- function WaterfallRow({ row, scale }: { row: Row; scale: number }) {
109
- const body = (
110
- <>
111
- <span
112
- className="col-start-1 row-start-1 flex min-w-0 items-center gap-1.5"
113
- style={{ paddingLeft: `${row.depth * 0.75}rem` }}
114
- >
115
- <KindBadge tone={row.failed ? "destructive" : "muted"}>
116
- {kindLabels[row.kind] ?? row.kind}
117
- </KindBadge>
118
- <span className="truncate font-mono">{row.label}</span>
119
- {row.server && (
120
- <span className="text-muted-foreground hidden shrink-0 font-mono text-[10px] sm:inline">
121
- {row.server}
122
- </span>
123
- )}
124
- </span>
125
- <span className="col-start-2 row-start-1 text-right font-mono tabular-nums md:col-start-3">
126
- {ms(row.duration)}
127
- </span>
128
- <span className="bg-muted/30 col-span-2 col-start-1 row-start-2 h-2 rounded md:col-span-1 md:col-start-2 md:row-start-1 md:h-3">
129
- <span
130
- className={cn(
131
- "block h-full rounded",
132
- row.failed ? "bg-danger" : row.href ? "bg-primary" : "bg-ok",
133
- )}
134
- style={{
135
- marginLeft: `${Math.min(99, (row.offset / scale) * 100)}%`,
136
- width: `${Math.max(0.5, (row.duration / scale) * 100)}%`,
137
- }}
138
- />
139
- </span>
140
- </>
141
- )
142
- const className =
143
- "grid grid-cols-[1fr_4rem] items-center gap-x-2 gap-y-1 border-b px-2 py-1.5 text-xs last:border-b-0 md:grid-cols-[minmax(0,18rem)_1fr_4rem]"
144
- return row.href ? (
145
- <Link href={row.href} className={cn(className, "hover:bg-muted/40")}>
146
- {body}
147
- </Link>
148
- ) : (
149
- <div className={cn(className, "text-muted-foreground")}>{body}</div>
150
- )
151
- }
152
-
153
- export default function TraceShow(p: Props) {
154
- const { environment } = usePage<SharedProps>().props
155
- const a = environment!.application_id
156
- const e = environment!.id
157
- const rows = flatten(p.roots, a, e)
158
- const executions = rows.filter((r) => r.href !== null)
159
- const errors = executions.filter((r) => r.failed).length
160
- const scale = Math.max(p.duration, 1)
161
-
162
- return (
163
- <EnvLayout title="Trace" crumbs={[{ title: "Trace", href: "#" }]}>
164
- <PageHeader
165
- withWindow={false}
166
- title={
167
- <span className="flex min-w-0 items-center gap-1">
168
- <span className="truncate font-mono text-base md:text-xl">
169
- {p.trace_id}
170
- </span>
171
- <CopyId value={p.trace_id} label="" />
172
- </span>
173
- }
174
- description="Every execution that shares this trace id, nested by the call that started it."
175
- />
176
- <StatStrip>
177
- <Stat
178
- label="Executions"
179
- value={count(executions.length)}
180
- hint={`${p.span_count} spans`}
181
- />
182
- <Stat
183
- label="Services"
184
- value={count(p.services.length)}
185
- hint={p.services.join(" · ")}
186
- />
187
- <Stat label="Total duration" value={ms(p.duration)} />
188
- <Stat
189
- label="Errors"
190
- value={count(errors)}
191
- tone={errors ? "destructive" : undefined}
192
- />
193
- </StatStrip>
194
- <div className="bg-card -mx-3 overflow-hidden border-y md:mx-0 md:rounded-lg md:border">
195
- {rows.length === 0 ? (
196
- <EmptyState
197
- title="Nothing in this trace"
198
- description="No execution reported this trace id."
199
- />
200
- ) : (
201
- rows.map((row) => (
202
- <WaterfallRow key={row.key} row={row} scale={scale} />
203
- ))
204
- )}
205
- </div>
206
- </EnvLayout>
207
- )
208
- }