railwatch 0.9.4 → 1.0.0

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