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,295 +0,0 @@
1
- import { Fragment, useState } from "react"
2
-
3
- import { SourceLink } from "@/components/railwatch/source-link"
4
- import { Badge } from "@/components/ui/badge"
5
- import { cn } from "@/lib/utils"
6
- import type { ExceptionDetail, Frame, TimelineEntry } from "@/types"
7
-
8
- // A hashed build asset -- "assets/index-Bq1x9K2v.js", what a browser frame
9
- // points at in production. It is the app's own code, but not at any path
10
- // that exists in the repository, so there is nothing to link it to until
11
- // Railwatch can resolve it through a source map (docs/roadmap.md); until then
12
- // it is shown exactly as the browser named it.
13
- const HASHED_ASSET = /-[A-Za-z0-9_-]{8,}\.(?:js|mjs|cjs|css)$/
14
-
15
- function FrameRow({
16
- frame,
17
- deploy,
18
- open,
19
- onToggle,
20
- }: {
21
- frame: Frame
22
- deploy?: string | null
23
- open: boolean
24
- onToggle: () => void
25
- }) {
26
- const lines = frame.code ? Object.entries(frame.code) : []
27
- const linkable = frame.in_app && !HASHED_ASSET.test(frame.file)
28
- return (
29
- <div
30
- className={cn(
31
- "border-b last:border-b-0",
32
- frame.in_app ? "bg-background" : "bg-muted/40",
33
- )}
34
- >
35
- <div className="hover:bg-muted/60 flex w-full items-center gap-2 px-3 py-1.5 font-mono text-xs">
36
- {linkable && (
37
- <SourceLink
38
- location={`${frame.file}:${frame.line}`}
39
- deploy={deploy}
40
- />
41
- )}
42
- <button
43
- type="button"
44
- onClick={onToggle}
45
- className="flex min-w-0 flex-1 items-center gap-2 text-left"
46
- >
47
- {!linkable && (
48
- <span className="text-muted-foreground truncate">
49
- {frame.file}:{frame.line}
50
- </span>
51
- )}
52
- <span className="text-muted-foreground ml-auto truncate">
53
- in {frame.function}
54
- </span>
55
- {frame.in_app && (
56
- <Badge variant="outline" className="shrink-0 text-[10px]">
57
- app
58
- </Badge>
59
- )}
60
- </button>
61
- </div>
62
- {open && lines.length > 0 && (
63
- <pre className="bg-muted/60 overflow-x-auto px-3 py-2 font-mono text-xs leading-5">
64
- {lines.map(([n, src]) => (
65
- <div
66
- key={n}
67
- className={cn(
68
- "flex gap-3 border-l-2 border-transparent pl-2",
69
- Number(n) === frame.line &&
70
- "border-destructive bg-destructive/10 font-semibold",
71
- )}
72
- >
73
- <span className="text-muted-foreground w-10 shrink-0 text-right select-none">
74
- {n}
75
- </span>
76
- <span className="whitespace-pre">{src}</span>
77
- </div>
78
- ))}
79
- </pre>
80
- )}
81
- </div>
82
- )
83
- }
84
-
85
- export function Frames({
86
- frames,
87
- deploy,
88
- }: {
89
- frames: Frame[]
90
- deploy?: string | null
91
- }) {
92
- const appIndexes = frames.flatMap((f, i) => (f.in_app ? [i] : []))
93
- const [open, setOpen] = useState<Set<number>>(
94
- new Set(appIndexes.length > 0 ? appIndexes : [0]),
95
- )
96
- const [showAll, setShowAll] = useState(false)
97
- const visible = showAll ? frames : frames.filter((f, i) => f.in_app || i < 3)
98
-
99
- function toggle(idx: number) {
100
- setOpen((prev) => {
101
- const next = new Set(prev)
102
- if (next.has(idx)) next.delete(idx)
103
- else next.add(idx)
104
- return next
105
- })
106
- }
107
-
108
- return (
109
- <div className="overflow-hidden rounded-lg border">
110
- {visible.map((f) => {
111
- const idx = frames.indexOf(f)
112
- return (
113
- <FrameRow
114
- key={idx}
115
- frame={f}
116
- deploy={deploy}
117
- open={open.has(idx)}
118
- onToggle={() => toggle(idx)}
119
- />
120
- )
121
- })}
122
- {visible.length < frames.length && (
123
- <button
124
- type="button"
125
- className="text-muted-foreground w-full px-3 py-1.5 text-left text-xs hover:underline"
126
- onClick={() => setShowAll(true)}
127
- >
128
- Show {frames.length - visible.length} framework frames
129
- </button>
130
- )}
131
- </div>
132
- )
133
- }
134
-
135
- export function ExceptionCard({
136
- exception,
137
- deploy,
138
- }: {
139
- exception: ExceptionDetail
140
- deploy?: string | null
141
- }) {
142
- return (
143
- <div className="space-y-3">
144
- <div>
145
- <div className="flex flex-wrap items-center gap-2">
146
- <span className="font-mono text-sm font-semibold">
147
- {exception.class_name}
148
- </span>
149
- <Badge variant={exception.handled ? "secondary" : "destructive"}>
150
- {exception.handled ? "handled" : "unhandled"}
151
- </Badge>
152
- {exception.severity && (
153
- <Badge variant="outline">{exception.severity}</Badge>
154
- )}
155
- {exception.source && (
156
- <span className="text-muted-foreground font-mono text-xs">
157
- {exception.source}
158
- </span>
159
- )}
160
- </div>
161
- <p className="mt-1 font-mono text-sm break-words">
162
- {exception.message}
163
- </p>
164
- {exception.cause && (
165
- <p className="text-muted-foreground mt-1 font-mono text-xs">
166
- caused by {exception.cause.class}: {exception.cause.message}
167
- </p>
168
- )}
169
- </div>
170
- <Frames frames={exception.frames} deploy={deploy} />
171
- {exception.locals && Object.keys(exception.locals).length > 0 && (
172
- <div className="overflow-hidden rounded-lg border">
173
- <div className="label-caps border-b px-3 py-1.5">
174
- Local variables at the raise site
175
- </div>
176
- <dl className="grid grid-cols-[max-content_1fr] gap-x-4 gap-y-1 px-3 py-2 font-mono text-xs">
177
- {Object.entries(exception.locals).map(([name, value]) => (
178
- <Fragment key={name}>
179
- <dt className="text-muted-foreground">{name}</dt>
180
- <dd className="break-all">{value}</dd>
181
- </Fragment>
182
- ))}
183
- </dl>
184
- </div>
185
- )}
186
- {exception.context && exception.context !== "{}" && (
187
- <pre className="bg-muted/60 overflow-x-auto rounded-lg p-3 font-mono text-xs">
188
- {exception.context}
189
- </pre>
190
- )}
191
- </div>
192
- )
193
- }
194
-
195
- const crumbColors: Record<string, string> = {
196
- query: "bg-sky-500",
197
- transaction: "bg-sky-300",
198
- cache_event: "bg-violet-500",
199
- outgoing_request: "bg-orange-500",
200
- mail: "bg-pink-500",
201
- broadcast: "bg-teal-500",
202
- enqueued_job: "bg-amber-500",
203
- view_render: "bg-emerald-500",
204
- storage_op: "bg-indigo-500",
205
- exception: "bg-red-500",
206
- log: "bg-neutral-400",
207
- }
208
-
209
- // Sentry-style breadcrumbs: what the execution did in the run-up to the
210
- // exception, oldest first, each with its offset from the start.
211
- export function Breadcrumbs({ entries }: { entries: TimelineEntry[] }) {
212
- if (entries.length === 0) return null
213
- return (
214
- <div className="overflow-hidden rounded-lg border">
215
- <div className="label-caps border-b px-3 py-1.5">
216
- Breadcrumbs · {entries.length} events before the exception
217
- </div>
218
- <ol className="divide-y">
219
- {entries.map((e) => (
220
- <li
221
- key={`${e.type}-${e.id}`}
222
- className="grid grid-cols-[3.5rem_5rem_1fr_4rem] items-center gap-2 px-3 py-1 font-mono text-xs"
223
- >
224
- <span className="text-muted-foreground tabular-nums">
225
- +{e.offset.toFixed(1)}ms
226
- </span>
227
- <span className="text-muted-foreground flex items-center gap-1.5 truncate">
228
- <span
229
- className={cn(
230
- "inline-block size-1.5 shrink-0 rounded-full",
231
- crumbColors[e.type] ?? "bg-neutral-500",
232
- )}
233
- />
234
- {e.type.replace("_", " ")}
235
- </span>
236
- <span className="truncate">{e.label}</span>
237
- <span className="text-muted-foreground text-right tabular-nums">
238
- {e.duration != null ? `${e.duration.toFixed(1)}ms` : ""}
239
- </span>
240
- </li>
241
- ))}
242
- </ol>
243
- </div>
244
- )
245
- }
246
-
247
- export interface BrowserCrumb {
248
- at: number
249
- kind: string
250
- text: string
251
- }
252
-
253
- const browserCrumbColors: Record<string, string> = {
254
- console: "bg-red-500",
255
- click: "bg-violet-500",
256
- navigate: "bg-sky-500",
257
- }
258
-
259
- // The browser's half of the same idea: what the user did in the run-up to a
260
- // JavaScript error -- console errors, clicks, Inertia navigations -- offset
261
- // from the first of them, since a browser error has no execution whose
262
- // timeline would mean anything (its execution is the beacon that carried it).
263
- export function BrowserBreadcrumbs({ crumbs }: { crumbs: BrowserCrumb[] }) {
264
- if (crumbs.length === 0) return null
265
- const start = crumbs[0].at
266
- return (
267
- <div className="overflow-hidden rounded-lg border">
268
- <div className="label-caps border-b px-3 py-1.5">
269
- Breadcrumbs · {crumbs.length} events before the error
270
- </div>
271
- <ol className="divide-y">
272
- {crumbs.map((c, i) => (
273
- <li
274
- key={`${c.at}-${c.kind}-${i}`}
275
- className="grid grid-cols-[3.5rem_5rem_1fr] items-center gap-2 px-3 py-1 font-mono text-xs"
276
- >
277
- <span className="text-muted-foreground tabular-nums">
278
- +{((c.at - start) / 1000).toFixed(1)}s
279
- </span>
280
- <span className="text-muted-foreground flex items-center gap-1.5 truncate">
281
- <span
282
- className={cn(
283
- "inline-block size-1.5 shrink-0 rounded-full",
284
- browserCrumbColors[c.kind] ?? "bg-neutral-500",
285
- )}
286
- />
287
- {c.kind}
288
- </span>
289
- <span className="truncate">{c.text}</span>
290
- </li>
291
- ))}
292
- </ol>
293
- </div>
294
- )
295
- }
@@ -1,159 +0,0 @@
1
- import { Check, ChevronDown, ChevronRight, Copy } from "lucide-react"
2
- import { useState } from "react"
3
-
4
- import { Button } from "@/components/ui/button"
5
- import { useClipboard } from "@/hooks/use-clipboard"
6
- import { cn } from "@/lib/utils"
7
-
8
- type Json = null | boolean | number | string | Json[] | { [key: string]: Json }
9
-
10
- function isContainer(v: unknown): v is Json[] | Record<string, Json> {
11
- return v !== null && typeof v === "object"
12
- }
13
-
14
- function primitiveClass(v: Json) {
15
- if (v === null) return "text-muted-foreground"
16
- if (typeof v === "string") return "text-emerald-600 dark:text-emerald-400"
17
- if (typeof v === "number") return "text-amber-600 dark:text-amber-400"
18
- if (typeof v === "boolean") return "text-primary"
19
- return ""
20
- }
21
-
22
- function Primitive({ value }: { value: string | number | boolean | null }) {
23
- const text =
24
- value === null
25
- ? "null"
26
- : typeof value === "string"
27
- ? `"${value}"`
28
- : String(value)
29
- return <span className={primitiveClass(value)}>{text}</span>
30
- }
31
-
32
- function Node({
33
- label,
34
- value,
35
- depth,
36
- collapsed,
37
- }: {
38
- label?: string
39
- value: Json
40
- depth: number
41
- collapsed: boolean
42
- }) {
43
- const [open, setOpen] = useState(!collapsed)
44
- const indent = { paddingLeft: `${depth * 12}px` }
45
-
46
- if (!isContainer(value)) {
47
- return (
48
- <div style={indent}>
49
- {label !== undefined && (
50
- <span className="text-muted-foreground">{label}: </span>
51
- )}
52
- <Primitive value={value} />
53
- </div>
54
- )
55
- }
56
-
57
- const entries = Array.isArray(value)
58
- ? value.map((v, i) => [String(i), v] as const)
59
- : Object.entries(value)
60
- const [open_, close_] = Array.isArray(value) ? ["[", "]"] : ["{", "}"]
61
-
62
- if (entries.length === 0) {
63
- return (
64
- <div style={indent}>
65
- {label !== undefined && (
66
- <span className="text-muted-foreground">{label}: </span>
67
- )}
68
- <span className="text-muted-foreground">
69
- {open_}
70
- {close_}
71
- </span>
72
- </div>
73
- )
74
- }
75
-
76
- return (
77
- <div>
78
- <button
79
- type="button"
80
- onClick={() => setOpen((o) => !o)}
81
- className="hover:bg-muted/60 inline-flex items-center gap-0.5 rounded"
82
- style={indent}
83
- >
84
- {open ? (
85
- <ChevronDown className="text-muted-foreground size-3" />
86
- ) : (
87
- <ChevronRight className="text-muted-foreground size-3" />
88
- )}
89
- {label !== undefined && (
90
- <span className="text-muted-foreground">{label}:</span>
91
- )}
92
- <span className="text-muted-foreground">
93
- {open_}
94
- {!open && `…${entries.length}…${close_}`}
95
- </span>
96
- </button>
97
- {open && (
98
- <div>
99
- {entries.map(([k, v]) => (
100
- <Node
101
- key={k}
102
- label={Array.isArray(value) ? undefined : k}
103
- value={v}
104
- depth={depth + 1}
105
- collapsed={collapsed}
106
- />
107
- ))}
108
- <div className="text-muted-foreground" style={indent}>
109
- {close_}
110
- </div>
111
- </div>
112
- )}
113
- </div>
114
- )
115
- }
116
-
117
- export function JsonViewer({
118
- data,
119
- collapsed = false,
120
- className,
121
- }: {
122
- data: unknown
123
- collapsed?: boolean
124
- className?: string
125
- }) {
126
- const [, copy] = useClipboard()
127
- const [copied, setCopied] = useState(false)
128
- return (
129
- <div
130
- className={cn(
131
- "bg-muted/60 relative rounded p-2 pr-8 font-mono text-xs",
132
- className,
133
- )}
134
- >
135
- <Button
136
- type="button"
137
- variant="ghost"
138
- size="icon-sm"
139
- className="absolute top-1 right-1"
140
- onClick={() => {
141
- void (async () => {
142
- const ok = await copy(JSON.stringify(data, null, 2))
143
- if (ok) {
144
- setCopied(true)
145
- setTimeout(() => setCopied(false), 1200)
146
- }
147
- })()
148
- }}
149
- >
150
- {copied ? (
151
- <Check className="size-3.5" />
152
- ) : (
153
- <Copy className="size-3.5" />
154
- )}
155
- </Button>
156
- <Node value={data as Json} depth={0} collapsed={collapsed} />
157
- </div>
158
- )
159
- }
@@ -1,78 +0,0 @@
1
- import { useTicker } from "@/hooks/use-ticker"
2
- import { cn } from "@/lib/utils"
3
-
4
- function isFresh(lastSeenAt: string | null, thresholdMs: number): boolean {
5
- if (lastSeenAt === null) return false
6
- return Date.now() - new Date(lastSeenAt).getTime() < thresholdMs
7
- }
8
-
9
- const UNLIT = "bg-neutral-400/15"
10
-
11
- // A three-aspect signal head standing in for the usual status dot, lamps
12
- // in their real order: stop on top, caution in the middle, clear at the
13
- // bottom. Fresh telemetry lights the bottom lamp green and pings it; stale
14
- // lights the middle one amber; nothing yet leaves all three unlit. `erroring`
15
- // lights the top lamp red instead, for an environment that is reporting
16
- // but reporting failures. Same colour vocabulary as the empty-state signal
17
- // and the status badges.
18
- export function LiveDot({
19
- lastSeenAt,
20
- thresholdMs = 60_000,
21
- erroring = false,
22
- className,
23
- }: {
24
- lastSeenAt: string | null
25
- thresholdMs?: number
26
- erroring?: boolean
27
- className?: string
28
- }) {
29
- useTicker()
30
- const fresh = isFresh(lastSeenAt, thresholdMs)
31
- const state =
32
- lastSeenAt === null
33
- ? "none"
34
- : erroring
35
- ? "stop"
36
- : fresh
37
- ? "clear"
38
- : "caution"
39
-
40
- return (
41
- <span
42
- className={cn(
43
- "border-foreground/30 bg-background/60 inline-flex w-2.5 shrink-0 flex-col items-center justify-between rounded-full border px-px py-[3px]",
44
- className,
45
- )}
46
- style={{ height: 24 }}
47
- >
48
- <span className="relative inline-flex size-1.5">
49
- {state === "stop" && (
50
- <span className="bg-danger absolute inline-flex h-full w-full animate-ping rounded-full opacity-75" />
51
- )}
52
- <span
53
- className={cn(
54
- "relative inline-flex size-1.5 rounded-full transition-colors duration-300",
55
- state === "stop" ? "bg-danger" : UNLIT,
56
- )}
57
- />
58
- </span>
59
- <span
60
- className={cn(
61
- "inline-flex size-1.5 rounded-full transition-colors duration-300",
62
- state === "caution" ? "bg-warning" : UNLIT,
63
- )}
64
- />
65
- <span className="relative inline-flex size-1.5">
66
- {state === "clear" && (
67
- <span className="bg-live absolute inline-flex h-full w-full animate-ping rounded-full opacity-75" />
68
- )}
69
- <span
70
- className={cn(
71
- "relative inline-flex size-1.5 rounded-full transition-colors duration-300",
72
- state === "clear" ? "bg-live" : UNLIT,
73
- )}
74
- />
75
- </span>
76
- </span>
77
- )
78
- }
@@ -1,76 +0,0 @@
1
- import { usePage } from "@inertiajs/react"
2
-
3
- import { badgeVariants } from "@/components/ui/badge"
4
- import {
5
- Tooltip,
6
- TooltipContent,
7
- TooltipTrigger,
8
- } from "@/components/ui/tooltip"
9
- import { useLive } from "@/hooks/use-live"
10
- import { useTicker } from "@/hooks/use-ticker"
11
- import { ago } from "@/lib/format"
12
- import { cn } from "@/lib/utils"
13
- import type { SharedProps } from "@/types"
14
-
15
- import { LiveDot } from "./live-dot"
16
-
17
- // Small pill embedded in every environment page's PageHeader: pulses while
18
- // live ingest events are flowing, click to pause/resume the automatic
19
- // background reloads that keep the page's data fresh.
20
- export function LiveToggle({ environmentId }: { environmentId: number }) {
21
- useTicker()
22
- const { environment, telemetry_freshness: freshness } =
23
- usePage<SharedProps>().props
24
- const { connected, lastEventAt, lastRefreshedAt, paused, setPaused } =
25
- useLive(environmentId)
26
- const lastEventIso =
27
- lastEventAt != null
28
- ? new Date(lastEventAt).toISOString()
29
- : (freshness?.received_at ?? environment?.last_seen_at ?? null)
30
- const refreshedIso =
31
- lastRefreshedAt != null ? new Date(lastRefreshedAt).toISOString() : null
32
-
33
- return (
34
- <Tooltip>
35
- <TooltipTrigger asChild>
36
- <button
37
- type="button"
38
- aria-pressed={paused}
39
- aria-label={paused ? "Resume live updates" : "Pause live updates"}
40
- onClick={() => setPaused(!paused)}
41
- className={cn(
42
- badgeVariants({ variant: "outline" }),
43
- "hover:bg-accent h-7 cursor-pointer gap-1.5 rounded-md font-mono text-[11px] tracking-wide uppercase",
44
- )}
45
- >
46
- <LiveDot
47
- lastSeenAt={connected ? lastEventIso : null}
48
- thresholdMs={10_000}
49
- />
50
- {paused ? "Paused" : connected ? "Live" : "Disconnected"}
51
- </button>
52
- </TooltipTrigger>
53
- <TooltipContent>
54
- <p>
55
- {lastEventIso
56
- ? `Telemetry received ${ago(lastEventIso)}`
57
- : "No telemetry received yet"}
58
- </p>
59
- <p>
60
- {refreshedIso
61
- ? `Page refreshed ${ago(refreshedIso)}`
62
- : "No live refresh yet"}
63
- </p>
64
- {freshness?.processing_lag_seconds != null && (
65
- <p>
66
- Ingest processing pending:{" "}
67
- {Math.round(freshness.processing_lag_seconds)}s
68
- </p>
69
- )}
70
- {freshness?.aggregated_at && (
71
- <p>Charts updated {ago(freshness.aggregated_at)}</p>
72
- )}
73
- </TooltipContent>
74
- </Tooltip>
75
- )
76
- }