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,205 +0,0 @@
1
- import { router } from "@inertiajs/react"
2
- import { useEffect, useRef, useState, useSyncExternalStore } from "react"
3
-
4
- import { getConsumer } from "@/lib/cable"
5
-
6
- const RELOAD_THROTTLE_MS = 5_000
7
- const PAUSED_STORAGE_KEY = "railwatch:live-paused"
8
-
9
- export interface LiveEvent {
10
- event: string
11
- at: string
12
- counts: Record<string, number>
13
- }
14
-
15
- // Whether live updates are actually flowing right now: subscribed and not
16
- // paused. The page's LiveToggle owns the one `useLive` subscription, so
17
- // anything else that needs the answer -- a number that only animates while
18
- // the data behind it is moving -- reads it from here rather than opening a
19
- // second WebSocket subscription of its own.
20
- let live = false
21
- const liveWatchers = new Set<() => void>()
22
-
23
- function publishLive(value: boolean) {
24
- if (live === value) return
25
- live = value
26
- for (const watcher of liveWatchers) watcher()
27
- }
28
-
29
- function subscribeLive(onChange: () => void) {
30
- liveWatchers.add(onChange)
31
- return () => {
32
- liveWatchers.delete(onChange)
33
- }
34
- }
35
-
36
- export function useIsLive() {
37
- return useSyncExternalStore(
38
- subscribeLive,
39
- () => live,
40
- () => false,
41
- )
42
- }
43
-
44
- // Subscribes to an environment's EnvironmentChannel stream. On each message,
45
- // reloads the page's Inertia data props in the background (a partial
46
- // `router.reload`, not a navigation), throttled to at most once every 5s and
47
- // only while the tab is visible; one reload fires on return to the tab if an
48
- // event arrived while it was hidden. Pass `only` to limit the reload to
49
- // specific props, matching whatever `router.reload({ only })` accepts;
50
- // omit it to refresh every prop the current page was rendered with.
51
- //
52
- // A visit the user makes (a window, range or filter change; anything that
53
- // is not itself a background reload) supersedes the reload: one in flight
54
- // is cancelled, since it is for the URL being left and would otherwise land
55
- // on top of the new page, none starts until the visit is over, and the
56
- // visit counts as the latest reload, so the throttle does not follow it
57
- // with a second fetch of what it just loaded.
58
- export function useLive(
59
- environmentId: number,
60
- {
61
- only,
62
- onEvent,
63
- }: { only?: string[]; onEvent?: (event: LiveEvent) => void } = {},
64
- ) {
65
- const [connected, setConnected] = useState(false)
66
- const [lastEventAt, setLastEventAt] = useState<number | null>(null)
67
- const [lastRefreshedAt, setLastRefreshedAt] = useState<number | null>(null)
68
- const [subscribedEnvironment, setSubscribedEnvironment] =
69
- useState(environmentId)
70
- if (subscribedEnvironment !== environmentId) {
71
- setSubscribedEnvironment(environmentId)
72
- setConnected(false)
73
- setLastEventAt(null)
74
- setLastRefreshedAt(null)
75
- }
76
- const [paused, setPausedState] = useState(() => {
77
- if (typeof window === "undefined") return false
78
- return localStorage.getItem(PAUSED_STORAGE_KEY) === "1"
79
- })
80
-
81
- const onlyRef = useRef(only)
82
- const onEventRef = useRef(onEvent)
83
- const pausedRef = useRef(paused)
84
- useEffect(() => {
85
- onlyRef.current = only
86
- onEventRef.current = onEvent
87
- pausedRef.current = paused
88
- })
89
-
90
- const scheduleRef = useRef<() => void>(() => undefined)
91
-
92
- useEffect(() => {
93
- publishLive(connected && !paused)
94
- return () => publishLive(false)
95
- }, [connected, paused])
96
-
97
- function setPaused(value: boolean) {
98
- pausedRef.current = value
99
- localStorage.setItem(PAUSED_STORAGE_KEY, value ? "1" : "0")
100
- setPausedState(value)
101
- scheduleRef.current()
102
- }
103
-
104
- useEffect(() => {
105
- let disposed = false
106
- let pending = false
107
- let inFlight = false
108
- let navigating = false
109
- let lastReloadAt = -Infinity
110
- let timer: ReturnType<typeof setTimeout> | undefined
111
- let cancelReload: (() => void) | undefined
112
-
113
- function reload() {
114
- pending = false
115
- inFlight = true
116
- lastReloadAt = Date.now()
117
- // router.reload always preserves scroll and state; that's why
118
- // ReloadOptions omits those keys (unlike router.visit).
119
- router.reload({
120
- only: onlyRef.current,
121
- onCancelToken: (token) => {
122
- cancelReload = () => token.cancel()
123
- },
124
- onSuccess: () => {
125
- if (!disposed) setLastRefreshedAt(Date.now())
126
- },
127
- onFinish: () => {
128
- cancelReload = undefined
129
- inFlight = false
130
- maybeReload()
131
- },
132
- })
133
- }
134
-
135
- function maybeReload() {
136
- clearTimeout(timer)
137
- timer = undefined
138
- if (
139
- disposed ||
140
- !pending ||
141
- inFlight ||
142
- navigating ||
143
- pausedRef.current ||
144
- document.visibilityState !== "visible"
145
- )
146
- return
147
- const delay = RELOAD_THROTTLE_MS - (Date.now() - lastReloadAt)
148
- if (delay > 0) timer = setTimeout(maybeReload, delay)
149
- else reload()
150
- }
151
-
152
- function onVisibilityChange() {
153
- maybeReload()
154
- }
155
-
156
- // `async` is every visit the page starts by itself: these reloads, a
157
- // poll, a prefetch, a deferred prop.
158
- const stopOnStart = router.on("start", (event) => {
159
- if (event.detail.visit.async) return
160
- navigating = true
161
- pending = false
162
- lastReloadAt = Date.now()
163
- cancelReload?.()
164
- })
165
- const stopOnFinish = router.on("finish", (event) => {
166
- if (event.detail.visit.async) return
167
- navigating = false
168
- maybeReload()
169
- })
170
-
171
- document.addEventListener("visibilitychange", onVisibilityChange)
172
- scheduleRef.current = maybeReload
173
- const subscription = getConsumer().subscriptions.create(
174
- { channel: "EnvironmentChannel", id: environmentId },
175
- {
176
- connected: () => {
177
- if (!disposed) setConnected(true)
178
- },
179
- disconnected: () => {
180
- if (!disposed) setConnected(false)
181
- },
182
- received: (data: LiveEvent) => {
183
- if (disposed) return
184
- setLastEventAt(Date.now())
185
- onEventRef.current?.(data)
186
- pending = true
187
- maybeReload()
188
- },
189
- },
190
- )
191
-
192
- return () => {
193
- disposed = true
194
- cancelReload?.()
195
- clearTimeout(timer)
196
- stopOnStart()
197
- stopOnFinish()
198
- scheduleRef.current = () => undefined
199
- document.removeEventListener("visibilitychange", onVisibilityChange)
200
- subscription.unsubscribe()
201
- }
202
- }, [environmentId])
203
-
204
- return { connected, lastEventAt, lastRefreshedAt, paused, setPaused }
205
- }
@@ -1,8 +0,0 @@
1
- import { useCallback } from "react"
2
-
3
- export function useMobileNavigation() {
4
- return useCallback(() => {
5
- // Remove pointer-events style from body...
6
- document.body.style.removeProperty("pointer-events")
7
- }, [])
8
- }
@@ -1,21 +0,0 @@
1
- import { useSyncExternalStore } from "react"
2
-
3
- const MOBILE_BREAKPOINT = 768
4
-
5
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
6
-
7
- function mediaQueryListener(callback: (event: MediaQueryListEvent) => void) {
8
- mql.addEventListener("change", callback)
9
-
10
- return () => {
11
- mql.removeEventListener("change", callback)
12
- }
13
- }
14
-
15
- function isSmallerThanBreakpoint() {
16
- return mql.matches
17
- }
18
-
19
- export function useIsMobile() {
20
- return useSyncExternalStore(mediaQueryListener, isSmallerThanBreakpoint)
21
- }
@@ -1,24 +0,0 @@
1
- import { router } from "@inertiajs/react"
2
-
3
- import type { Percentile } from "@/types"
4
-
5
- const PERCENTILES: Percentile[] = ["p50", "p95", "p99"]
6
-
7
- // Client-only URL param (?pct=), not inertia_share'd like window: every
8
- // list page reads/writes it the same way the count/p95 sort toggle already
9
- // does with router.visit + preserveState.
10
- export function usePercentile() {
11
- const url = new URL(globalThis.location.href)
12
- const raw = url.searchParams.get("pct")
13
- const percentile: Percentile = PERCENTILES.includes(raw as Percentile)
14
- ? (raw as Percentile)
15
- : "p95"
16
-
17
- const set = (value: Percentile) => {
18
- const next = new URL(globalThis.location.href)
19
- next.searchParams.set("pct", value)
20
- router.visit(next.toString(), { preserveScroll: true, preserveState: true })
21
- }
22
-
23
- return { percentile, set }
24
- }
@@ -1,25 +0,0 @@
1
- import { useSyncExternalStore } from "react"
2
-
3
- const QUERY = "(prefers-reduced-motion: reduce)"
4
-
5
- // Whether the user has asked their OS for less motion. Read in JavaScript
6
- // rather than left to CSS: a global `animation: none` does not stop a Web
7
- // Animations API animation, so a component that animates has to decide not
8
- // to. Follows the media query live, so turning the setting on stops the
9
- // motion without a reload.
10
- function subscribe(onChange: () => void) {
11
- const query = window.matchMedia(QUERY)
12
- query.addEventListener("change", onChange)
13
- return () => {
14
- query.removeEventListener("change", onChange)
15
- }
16
- }
17
-
18
- function snapshot() {
19
- return window.matchMedia(QUERY).matches
20
- }
21
-
22
- export function useReducedMotion() {
23
- // Server-rendered markup carries no motion, so it renders as if reduced.
24
- return useSyncExternalStore(subscribe, snapshot, () => true)
25
- }
@@ -1,106 +0,0 @@
1
- import { useEffect, useRef, useState } from "react"
2
-
3
- import { ignorePageShortcut } from "@/lib/keyboard"
4
-
5
- const navigators = new Map<symbol, () => void>()
6
- let activeNavigator: symbol | undefined
7
-
8
- export interface RowNavOptions<T> {
9
- rows: T[]
10
- rowKey: (row: T) => string | number
11
- onOpen: (row: T, opts?: { newTab?: boolean }) => void
12
- enabled?: boolean
13
- }
14
-
15
- // j/k highlight a row, Enter opens it (onOpen), "o" opens it in a new tab
16
- // (onOpen with newTab: true), Esc clears the highlight, and "/" focuses the
17
- // page's FilterBar input (matched by `[data-filter-bar-input]`) if present.
18
- // Ignored while focus is in a form field or a modifier key is held. Backs
19
- // `DataTable`'s `keyboardNav` prop; extracted here so every list page shares
20
- // one implementation.
21
- export function useRowNav<T>({
22
- rows,
23
- rowKey,
24
- onOpen,
25
- enabled = true,
26
- }: RowNavOptions<T>) {
27
- const [highlighted, setHighlighted] = useState<string | number | null>(null)
28
- const [identity] = useState(() => Symbol("row-nav"))
29
- function activate() {
30
- if (!enabled || activeNavigator === identity) return
31
- if (activeNavigator) navigators.get(activeNavigator)?.()
32
- activeNavigator = identity
33
- }
34
-
35
- useEffect(() => {
36
- if (!enabled) return
37
- navigators.set(identity, () => setHighlighted(null))
38
- activeNavigator ??= identity
39
- return () => {
40
- navigators.delete(identity)
41
- if (activeNavigator === identity)
42
- activeNavigator = navigators.keys().next().value
43
- }
44
- }, [enabled, identity])
45
-
46
- const rowsRef = useRef(rows)
47
- const rowKeyRef = useRef(rowKey)
48
- const onOpenRef = useRef(onOpen)
49
- useEffect(() => {
50
- rowsRef.current = rows
51
- rowKeyRef.current = rowKey
52
- onOpenRef.current = onOpen
53
- })
54
-
55
- useEffect(() => {
56
- if (!enabled) return
57
-
58
- function onKeyDown(event: KeyboardEvent) {
59
- if (activeNavigator !== identity || ignorePageShortcut(event)) return
60
-
61
- if (event.key === "/") {
62
- const input = document.querySelector<HTMLElement>(
63
- "[data-filter-bar-input]",
64
- )
65
- if (input) {
66
- event.preventDefault()
67
- input.focus()
68
- }
69
- return
70
- }
71
-
72
- const currentRows = rowsRef.current
73
- const key = rowKeyRef.current
74
- const index = currentRows.findIndex((r) => key(r) === highlighted)
75
-
76
- if (event.key === "j") {
77
- if (currentRows.length === 0) return
78
- event.preventDefault()
79
- const next = Math.min(index + 1, currentRows.length - 1)
80
- setHighlighted(key(currentRows[next]))
81
- } else if (event.key === "k") {
82
- if (currentRows.length === 0) return
83
- event.preventDefault()
84
- const prev = index < 0 ? 0 : Math.max(index - 1, 0)
85
- setHighlighted(key(currentRows[prev]))
86
- } else if (event.key === "Enter") {
87
- if (index >= 0) {
88
- event.preventDefault()
89
- onOpenRef.current(currentRows[index])
90
- }
91
- } else if (event.key === "o") {
92
- if (index >= 0) {
93
- event.preventDefault()
94
- onOpenRef.current(currentRows[index], { newTab: true })
95
- }
96
- } else if (event.key === "Escape") {
97
- setHighlighted(null)
98
- }
99
- }
100
-
101
- document.addEventListener("keydown", onKeyDown)
102
- return () => document.removeEventListener("keydown", onKeyDown)
103
- }, [highlighted, enabled, identity])
104
-
105
- return { highlighted, setHighlighted, activate }
106
- }
@@ -1,27 +0,0 @@
1
- import { router, usePage } from "@inertiajs/react"
2
-
3
- import type { SharedProps, Step } from "@/types"
4
-
5
- export const STEP_LABELS: Record<Step, string> = {
6
- "1m": "1 minute",
7
- "5m": "5 minutes",
8
- "15m": "15 minutes",
9
- "1h": "1 hour",
10
- "6h": "6 hours",
11
- "1d": "1 day",
12
- }
13
-
14
- // The chart bucket width (?step=) and the widths the current window offers,
15
- // both inertia_share'd by EnvironmentScoped like `window` is. Changing the
16
- // window drops the step so the new window draws at its own default.
17
- export function useStep() {
18
- const { step = "1h", steps = [] } = usePage<SharedProps>().props
19
-
20
- const set = (value: Step) => {
21
- const url = new URL(globalThis.location.href)
22
- url.searchParams.set("step", value)
23
- router.visit(url.toString(), { preserveScroll: true, preserveState: true })
24
- }
25
-
26
- return { step, steps, set, label: STEP_LABELS[step] ?? step }
27
- }
@@ -1,12 +0,0 @@
1
- import { useEffect, useState } from "react"
2
-
3
- // Re-renders the calling component every `intervalMs`, so relative-time
4
- // labels ("2m ago") and freshness checks stay current without a reload.
5
- export function useTicker(intervalMs = 30_000) {
6
- const [, setTick] = useState(0)
7
-
8
- useEffect(() => {
9
- const id = window.setInterval(() => setTick((tick) => tick + 1), intervalMs)
10
- return () => window.clearInterval(id)
11
- }, [intervalMs])
12
- }
@@ -1,58 +0,0 @@
1
- import { router, usePage } from "@inertiajs/react"
2
-
3
- import type { SharedProps, Window } from "@/types"
4
-
5
- export const WINDOWS: { value: Window; label: string }[] = [
6
- { value: "1h", label: "1 hour" },
7
- { value: "6h", label: "6 hours" },
8
- { value: "24h", label: "24 hours" },
9
- { value: "7d", label: "7 days" },
10
- { value: "30d", label: "30 days" },
11
- ]
12
-
13
- function formatRange(from: string, to: string) {
14
- const fmt: Intl.DateTimeFormatOptions = {
15
- month: "short",
16
- day: "numeric",
17
- hour: "2-digit",
18
- minute: "2-digit",
19
- }
20
- return `${new Date(from).toLocaleString(undefined, fmt)} – ${new Date(to).toLocaleString(undefined, fmt)}`
21
- }
22
-
23
- export function useWindow() {
24
- const { window: current = "24h", range } = usePage<SharedProps>().props
25
-
26
- const set = (value: Window) => {
27
- const url = new URL(globalThis.location.href)
28
- url.searchParams.set("window", value)
29
- url.searchParams.delete("from")
30
- url.searchParams.delete("to")
31
- url.searchParams.delete("step")
32
- router.visit(url.toString(), { preserveScroll: true, preserveState: true })
33
- }
34
-
35
- // Sets an explicit ?from=&to= range, dropping the fixed `window` param.
36
- const setRange = (from: string, to: string) => {
37
- const url = new URL(globalThis.location.href)
38
- url.searchParams.set("from", from)
39
- url.searchParams.set("to", to)
40
- url.searchParams.delete("window")
41
- url.searchParams.delete("step")
42
- router.visit(url.toString(), { preserveScroll: true, preserveState: true })
43
- }
44
-
45
- const label =
46
- current === "custom" && range
47
- ? formatRange(range.from, range.to)
48
- : (WINDOWS.find((w) => w.value === current)?.label ?? current)
49
-
50
- return {
51
- window: current,
52
- from: range?.from,
53
- to: range?.to,
54
- set,
55
- setRange,
56
- label,
57
- }
58
- }
@@ -1,107 +0,0 @@
1
- import { router, usePage } from "@inertiajs/react"
2
- import { type PropsWithChildren, useEffect, useState } from "react"
3
-
4
- import { AppContent } from "@/components/app-content"
5
- import { AppShell } from "@/components/app-shell"
6
- import { AppSidebar } from "@/components/app-sidebar"
7
- import { AppSidebarHeader } from "@/components/app-sidebar-header"
8
- import { CommandPalette } from "@/components/railwatch/command-palette"
9
- import { ShortcutsDialog } from "@/components/railwatch/shortcuts-dialog"
10
- import { useHotkeys } from "@/hooks/use-hotkeys"
11
- import {
12
- applicationEnvironmentJobsPath,
13
- applicationEnvironmentOverviewPath,
14
- applicationEnvironmentQueriesPath,
15
- applicationEnvironmentRequestsPath,
16
- dashboardPath,
17
- issuesPath,
18
- } from "@/routes"
19
- import type { BreadcrumbItem, SharedProps } from "@/types"
20
-
21
- export default function AppSidebarLayout({
22
- children,
23
- breadcrumbs = [],
24
- }: PropsWithChildren<{
25
- breadcrumbs?: BreadcrumbItem[]
26
- }>) {
27
- const [paletteOpen, setPaletteOpen] = useState(false)
28
- const [shortcutsOpen, setShortcutsOpen] = useState(false)
29
- const { environment, window } = usePage<SharedProps>().props
30
-
31
- useEffect(() => {
32
- const onKeyDown = (event: KeyboardEvent) => {
33
- if (event.key === "k" && (event.metaKey || event.ctrlKey)) {
34
- event.preventDefault()
35
- setPaletteOpen((open) => !open)
36
- }
37
- }
38
- document.addEventListener("keydown", onKeyDown)
39
- return () => document.removeEventListener("keydown", onKeyDown)
40
- }, [])
41
-
42
- // "g" then a letter jumps to the current environment's Overview/Requests/
43
- // Jobs/Queries/Issues when a page has one loaded (env-scoped pages share
44
- // it via EnvironmentScoped); otherwise "g i" falls back to global issues.
45
- useHotkeys({
46
- "?": () => setShortcutsOpen((open) => !open),
47
- "g d": () => router.visit(dashboardPath()),
48
- "g i": () =>
49
- router.visit(
50
- environment
51
- ? issuesPath({
52
- application_id: environment.application_id,
53
- environment_id: environment.id,
54
- })
55
- : issuesPath(),
56
- ),
57
- ...(environment && {
58
- "g o": () =>
59
- router.visit(
60
- applicationEnvironmentOverviewPath(
61
- environment.application_id,
62
- environment.id,
63
- { window },
64
- ),
65
- ),
66
- "g r": () =>
67
- router.visit(
68
- applicationEnvironmentRequestsPath(
69
- environment.application_id,
70
- environment.id,
71
- { window },
72
- ),
73
- ),
74
- "g j": () =>
75
- router.visit(
76
- applicationEnvironmentJobsPath(
77
- environment.application_id,
78
- environment.id,
79
- { window },
80
- ),
81
- ),
82
- "g q": () =>
83
- router.visit(
84
- applicationEnvironmentQueriesPath(
85
- environment.application_id,
86
- environment.id,
87
- { window },
88
- ),
89
- ),
90
- }),
91
- })
92
-
93
- return (
94
- <AppShell variant="sidebar">
95
- <AppSidebar />
96
- <AppContent variant="sidebar" className="overflow-x-hidden">
97
- <AppSidebarHeader
98
- breadcrumbs={breadcrumbs}
99
- onOpenCommandPalette={() => setPaletteOpen(true)}
100
- />
101
- {children}
102
- </AppContent>
103
- <CommandPalette open={paletteOpen} onOpenChange={setPaletteOpen} />
104
- <ShortcutsDialog open={shortcutsOpen} onOpenChange={setShortcutsOpen} />
105
- </AppShell>
106
- )
107
- }
@@ -1,21 +0,0 @@
1
- import type { ReactNode } from "react"
2
-
3
- import AppLayoutTemplate from "@/layouts/app/app-sidebar-layout"
4
- import type { BreadcrumbItem } from "@/types"
5
-
6
- interface AppLayoutProps {
7
- children: ReactNode
8
- breadcrumbs?: BreadcrumbItem[]
9
- }
10
-
11
- export default function AppLayout({
12
- children,
13
- breadcrumbs,
14
- ...props
15
- }: AppLayoutProps) {
16
- return (
17
- <AppLayoutTemplate breadcrumbs={breadcrumbs} {...props}>
18
- {children}
19
- </AppLayoutTemplate>
20
- )
21
- }
@@ -1,53 +0,0 @@
1
- import { Head, usePage } from "@inertiajs/react"
2
- import type { ReactNode } from "react"
3
-
4
- import AppLayout from "@/layouts/app-layout"
5
- import { applicationEnvironmentOverviewPath, applicationPath } from "@/routes"
6
- import type { BreadcrumbItem, SharedProps } from "@/types"
7
-
8
- // Layout for every environment-scoped telemetry page: breadcrumbs from the
9
- // shared environment prop, page title, and consistent padding.
10
- export default function EnvLayout({
11
- title,
12
- children,
13
- crumbs = [],
14
- }: {
15
- title: string
16
- children: ReactNode
17
- crumbs?: BreadcrumbItem[]
18
- }) {
19
- const { environment } = usePage<SharedProps>().props
20
- const base: BreadcrumbItem[] = environment
21
- ? [
22
- {
23
- title: environment.application_name,
24
- href: applicationPath(environment.application_id),
25
- },
26
- {
27
- title: environment.name,
28
- href: applicationEnvironmentOverviewPath(
29
- environment.application_id,
30
- environment.id,
31
- ),
32
- },
33
- ]
34
- : []
35
- const breadcrumbs = [
36
- ...base,
37
- ...(crumbs.length ? crumbs : [{ title, href: "#" }]),
38
- ]
39
- return (
40
- <AppLayout breadcrumbs={breadcrumbs}>
41
- <Head
42
- title={
43
- environment
44
- ? `${title} · ${environment.application_name} ${environment.name}`
45
- : title
46
- }
47
- />
48
- <div className="flex flex-1 flex-col gap-4 p-3 md:gap-5 md:p-6">
49
- {children}
50
- </div>
51
- </AppLayout>
52
- )
53
- }