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,405 +0,0 @@
1
- import { useMemo, useState } from "react"
2
-
3
- import { SqlBlock } from "@/components/railwatch/sql-block"
4
- import {
5
- HoverCard,
6
- HoverCardContent,
7
- HoverCardTrigger,
8
- } from "@/components/ui/hover-card"
9
- import { ms, usd } from "@/lib/format"
10
- import { cn } from "@/lib/utils"
11
- import type { TimelineEntry } from "@/types"
12
-
13
- const colors: Record<string, string> = {
14
- query: "bg-sky-500",
15
- transaction: "bg-sky-300",
16
- cache_event: "bg-violet-500",
17
- outgoing_request: "bg-orange-500",
18
- mail: "bg-pink-500",
19
- broadcast: "bg-teal-500",
20
- enqueued_job: "bg-amber-500",
21
- view_render: "bg-emerald-500",
22
- storage_op: "bg-indigo-500",
23
- exception: "bg-red-500",
24
- log: "bg-neutral-400",
25
- span: "bg-fuchsia-500",
26
- attachment: "bg-lime-500",
27
- llm_call: "bg-cyan-400",
28
- }
29
-
30
- const typeLabels: Record<string, string> = {
31
- query: "Query",
32
- cache_event: "Cache",
33
- outgoing_request: "Outgoing",
34
- mail: "Mail",
35
- broadcast: "Broadcast",
36
- log: "Log",
37
- exception: "Exception",
38
- view_render: "View",
39
- storage_op: "Storage",
40
- enqueued_job: "Job",
41
- transaction: "Transaction",
42
- span: "Span",
43
- attachment: "Attachment",
44
- llm_call: "LLM",
45
- }
46
-
47
- const stageOrder = [
48
- "middleware_before",
49
- "action",
50
- "render",
51
- "middleware_after",
52
- "body",
53
- ]
54
-
55
- const stageLabels: Record<string, string> = {
56
- middleware_before: "before middleware",
57
- action: "action",
58
- render: "render",
59
- middleware_after: "after middleware",
60
- body: "body",
61
- }
62
-
63
- // Candidate gridline intervals (ms); pick the smallest that keeps the axis
64
- // under ~8 ticks for the current span.
65
- const INTERVALS = [
66
- 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000,
67
- ]
68
-
69
- function pickInterval(span: number) {
70
- const target = span / 8
71
- return INTERVALS.find((i) => i >= target) ?? INTERVALS[INTERVALS.length - 1]
72
- }
73
-
74
- function axisTicks(span: number, interval: number) {
75
- const out: number[] = []
76
- for (let t = 0; t <= span; t += interval) out.push(t)
77
- return out
78
- }
79
-
80
- // Row/axis/stage-bar all share this grid so the ms axis and gridlines line
81
- // up exactly with every bar regardless of content.
82
- // Phones drop the type column and shrink the label so the bars keep room.
83
- const GRID =
84
- "grid grid-cols-[6rem_1fr_3.5rem] items-center gap-2 md:grid-cols-[5rem_13rem_1fr_4rem]"
85
- const TYPE_COL = "hidden md:block"
86
-
87
- function EntryBody({
88
- entry,
89
- showSource,
90
- }: {
91
- entry: TimelineEntry
92
- showSource?: boolean
93
- }) {
94
- const d = entry.detail ?? {}
95
- return (
96
- <div className="space-y-1.5">
97
- <div className="text-muted-foreground flex items-center gap-2 text-[10px] tracking-wide uppercase">
98
- <span>{typeLabels[entry.type] ?? entry.type}</span>
99
- {entry.stage && (
100
- <span>· {stageLabels[entry.stage] ?? entry.stage}</span>
101
- )}
102
- </div>
103
- {entry.type === "query" && entry.sql ? (
104
- <SqlBlock sql={entry.sql} wrap className="text-[11px]" />
105
- ) : entry.type === "cache_event" ? (
106
- <div className="font-mono text-xs break-all">
107
- {String(d.op ?? "")}{" "}
108
- <span className="font-semibold">{String(d.key ?? "")}</span>
109
- </div>
110
- ) : entry.type === "outgoing_request" ? (
111
- <div className="font-mono text-xs break-all">
112
- {String(d.method ?? "")} {String(d.url ?? entry.label)}
113
- {d.status != null && (
114
- <span className="text-muted-foreground"> → {String(d.status)}</span>
115
- )}
116
- {showSource && typeof d.body === "string" && d.body !== "" && (
117
- <pre className="bg-muted mt-1.5 max-h-48 overflow-auto rounded p-2 text-[10px] break-all whitespace-pre-wrap">
118
- {d.body}
119
- </pre>
120
- )}
121
- </div>
122
- ) : entry.type === "llm_call" ? (
123
- <div className="font-mono text-xs break-all">
124
- {String(d.tool ?? entry.label)}
125
- {d.input_tokens != null && (
126
- <span className="text-muted-foreground">
127
- {" "}
128
- · {String(d.input_tokens)} in / {String(d.output_tokens ?? 0)} out
129
- </span>
130
- )}
131
- {d.cost != null && (
132
- <span className="text-muted-foreground">
133
- {" "}
134
- · {usd(Number(d.cost))}
135
- </span>
136
- )}
137
- {d.step != null && (
138
- <span className="text-muted-foreground">
139
- {" "}
140
- · {String(d.workflow ?? "")} / {String(d.step)}
141
- </span>
142
- )}
143
- {d.error != null && (
144
- <div className="text-destructive mt-1">{String(d.error)}</div>
145
- )}
146
- </div>
147
- ) : entry.type === "mail" ? (
148
- <div className="font-mono text-xs break-all">
149
- {String(d.subject ?? entry.label)}
150
- </div>
151
- ) : entry.type === "log" ? (
152
- <div className="font-mono text-xs break-words">
153
- <span className="text-muted-foreground uppercase">
154
- {String(d.level ?? "")}
155
- </span>{" "}
156
- {String(d.message ?? entry.label)}
157
- </div>
158
- ) : entry.type === "exception" ? (
159
- <div className="text-destructive font-mono text-xs break-words">
160
- {entry.label}
161
- </div>
162
- ) : (
163
- <div className="font-mono text-xs break-words">{entry.label}</div>
164
- )}
165
- <div className="text-muted-foreground flex flex-wrap gap-x-3 text-[11px] tabular-nums">
166
- <span>+{ms(entry.offset)}</span>
167
- {entry.duration != null && <span>{ms(entry.duration)}</span>}
168
- </div>
169
- {showSource && entry.source && (
170
- <div className="text-muted-foreground border-t pt-1 font-mono text-[11px] break-all">
171
- {entry.source}
172
- </div>
173
- )}
174
- </div>
175
- )
176
- }
177
-
178
- function Gridlines({ ticks, span }: { ticks: number[]; span: number }) {
179
- return (
180
- <>
181
- {ticks.map((t) => (
182
- <div
183
- key={t}
184
- className="bg-border/60 pointer-events-none absolute top-0 h-full w-px"
185
- style={{ left: `${(t / span) * 100}%` }}
186
- />
187
- ))}
188
- </>
189
- )
190
- }
191
-
192
- export function Timeline({
193
- entries,
194
- total,
195
- stages,
196
- }: {
197
- entries: TimelineEntry[]
198
- total: number
199
- stages: Record<string, number>
200
- }) {
201
- const [hidden, setHidden] = useState<Set<string>>(new Set())
202
- const [expanded, setExpanded] = useState<string | null>(null)
203
-
204
- const span = Math.max(
205
- total,
206
- ...entries.map((e) => e.offset + (e.duration ?? 0)),
207
- 1,
208
- )
209
- const interval = pickInterval(span)
210
- const ticks = axisTicks(span, interval)
211
-
212
- const counts = useMemo(() => {
213
- const c = new Map<string, number>()
214
- for (const e of entries) c.set(e.type, (c.get(e.type) ?? 0) + 1)
215
- return c
216
- }, [entries])
217
-
218
- const visible = entries.filter((e) => !hidden.has(e.type))
219
-
220
- function toggleType(type: string) {
221
- setHidden((prev) => {
222
- const next = new Set(prev)
223
- if (next.has(type)) next.delete(type)
224
- else next.add(type)
225
- return next
226
- })
227
- }
228
-
229
- const stageBars = stageOrder
230
- .filter((s) => stages[s] !== undefined)
231
- .reduce<{ name: string; start: number; width: number }[]>((acc, s) => {
232
- const start =
233
- acc.length > 0
234
- ? acc[acc.length - 1].start + acc[acc.length - 1].width
235
- : 0
236
- acc.push({ name: s, start, width: stages[s] })
237
- return acc
238
- }, [])
239
-
240
- return (
241
- <div className="space-y-2">
242
- {counts.size > 0 && (
243
- <div className="flex flex-wrap items-center gap-1.5">
244
- {[...counts.entries()].map(([type, n]) => (
245
- <button
246
- key={type}
247
- type="button"
248
- onClick={() => toggleType(type)}
249
- className={cn(
250
- "flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-[10px] font-medium transition-colors",
251
- hidden.has(type)
252
- ? "text-muted-foreground opacity-50"
253
- : "hover:bg-muted/60",
254
- )}
255
- >
256
- <span
257
- className={cn(
258
- "size-1.5 rounded-full",
259
- colors[type] ?? "bg-neutral-500",
260
- )}
261
- />
262
- {typeLabels[type] ?? type}
263
- <span className="text-muted-foreground tabular-nums">{n}</span>
264
- </button>
265
- ))}
266
- </div>
267
- )}
268
-
269
- <div className={cn(GRID, "px-2 text-[10px]")}>
270
- <div className={TYPE_COL} />
271
- <div />
272
- <div className="relative h-4 select-none">
273
- {ticks.map((t) => (
274
- <span
275
- key={t}
276
- className="text-muted-foreground absolute -translate-x-1/2 tabular-nums"
277
- style={{ left: `${(t / span) * 100}%` }}
278
- >
279
- {ms(t, 0)}
280
- </span>
281
- ))}
282
- </div>
283
- <div />
284
- </div>
285
-
286
- <div className={cn(GRID, "px-2")}>
287
- <div className={TYPE_COL} />
288
- <div />
289
- <div className="bg-muted/40 relative h-5 overflow-hidden rounded text-[10px]">
290
- <Gridlines ticks={ticks} span={span} />
291
- {stageBars.map((s) => (
292
- <div
293
- key={s.name}
294
- className="border-background bg-primary/20 absolute top-0 flex h-full items-center overflow-hidden border-r px-1 whitespace-nowrap"
295
- style={{
296
- left: `${(s.start / span) * 100}%`,
297
- width: `${(s.width / span) * 100}%`,
298
- }}
299
- title={`${stageLabels[s.name] ?? s.name} ${ms(s.width)}`}
300
- >
301
- {stageLabels[s.name] ?? s.name} {ms(s.width, 0)}
302
- </div>
303
- ))}
304
- </div>
305
- <div />
306
- </div>
307
-
308
- <div className="max-h-[32rem] overflow-y-auto rounded-lg border">
309
- {entries.length === 0 && (
310
- <div className="text-muted-foreground p-4 text-center text-sm">
311
- No child events recorded.
312
- </div>
313
- )}
314
- {entries.length > 0 && visible.length === 0 && (
315
- <div className="text-muted-foreground p-4 text-center text-sm">
316
- No events match the active filters.
317
- </div>
318
- )}
319
- {visible.map((e) => {
320
- const key = `${e.type}-${e.id}`
321
- const isSlow = span > 0 && (e.duration ?? 0) / span > 0.1
322
- const isException = e.type === "exception"
323
- return (
324
- <div
325
- key={key}
326
- className="border-b last:border-b-0"
327
- style={{
328
- contentVisibility: "auto",
329
- containIntrinsicSize: "0 28px",
330
- }}
331
- >
332
- <HoverCard openDelay={150}>
333
- <HoverCardTrigger asChild>
334
- <button
335
- type="button"
336
- onClick={() => setExpanded(expanded === key ? null : key)}
337
- className={cn(
338
- GRID,
339
- "hover:bg-muted/40 w-full px-2 py-1 text-left text-xs",
340
- )}
341
- >
342
- <span
343
- className={cn(
344
- "text-muted-foreground truncate font-mono",
345
- TYPE_COL,
346
- )}
347
- >
348
- {typeLabels[e.type] ?? e.type}
349
- </span>
350
- {e.type === "query" && e.sql ? (
351
- <SqlBlock sql={e.sql} />
352
- ) : (
353
- <span className="truncate font-mono">{e.label}</span>
354
- )}
355
- <div className="bg-muted/30 relative h-3 rounded">
356
- <Gridlines ticks={ticks} span={span} />
357
- {isException ? (
358
- <div
359
- className="absolute top-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2 rotate-45 bg-red-500"
360
- style={{
361
- left: `${Math.min(100, (e.offset / span) * 100)}%`,
362
- }}
363
- />
364
- ) : (
365
- <div
366
- className={cn(
367
- "absolute top-0 h-full rounded",
368
- colors[e.type] ?? "bg-neutral-500",
369
- isSlow
370
- ? "ring-foreground/30 opacity-100 ring-1 ring-inset"
371
- : "opacity-70",
372
- )}
373
- style={{
374
- left: `${Math.min(100, (e.offset / span) * 100)}%`,
375
- width: `${Math.max(0.3, ((e.duration ?? 0) / span) * 100)}%`,
376
- }}
377
- />
378
- )}
379
- </div>
380
- <span
381
- className={cn(
382
- "text-muted-foreground text-right tabular-nums",
383
- isSlow && "text-foreground font-semibold",
384
- )}
385
- >
386
- {e.duration != null ? ms(e.duration) : "–"}
387
- </span>
388
- </button>
389
- </HoverCardTrigger>
390
- <HoverCardContent className="w-80" align="start">
391
- <EntryBody entry={e} />
392
- </HoverCardContent>
393
- </HoverCard>
394
- {expanded === key && (
395
- <div className="bg-muted/20 border-t px-4 py-2">
396
- <EntryBody entry={e} showSource />
397
- </div>
398
- )}
399
- </div>
400
- )
401
- })}
402
- </div>
403
- </div>
404
- )
405
- }
@@ -1,44 +0,0 @@
1
- import { cn } from "@/lib/utils"
2
-
3
- // busy-out-of-max as a hairline bar plus the raw pair, for thread pools and
4
- // connection pools. Same tone thresholds the Processes stat strip uses:
5
- // amber from 70%, red from 90%.
6
- export function UtilisationBar({
7
- busy,
8
- max,
9
- className,
10
- }: {
11
- busy: number | null
12
- max: number | null
13
- className?: string
14
- }) {
15
- if (!max) return <span className="text-muted-foreground">–</span>
16
- const ratio = Math.min(1, (busy ?? 0) / max)
17
- return (
18
- <span className={cn("flex items-center gap-1.5", className)}>
19
- <span
20
- role="meter"
21
- aria-label="Utilisation"
22
- aria-valuenow={busy ?? 0}
23
- aria-valuemin={0}
24
- aria-valuemax={max}
25
- className="bg-muted-foreground/25 relative h-1.5 w-10 shrink-0 overflow-hidden rounded-full"
26
- >
27
- <span
28
- className={cn(
29
- "absolute inset-y-0 left-0 rounded-full",
30
- ratio >= 0.9
31
- ? "bg-danger"
32
- : ratio >= 0.7
33
- ? "bg-warning"
34
- : "bg-live",
35
- )}
36
- style={{ width: `${Math.max(4, ratio * 100)}%` }}
37
- />
38
- </span>
39
- <span className="font-mono tabular-nums">
40
- {busy ?? 0}/{max}
41
- </span>
42
- </span>
43
- )
44
- }
@@ -1,147 +0,0 @@
1
- import { CalendarClock } from "lucide-react"
2
- import { useState } from "react"
3
-
4
- import { Segmented, SegmentedItem } from "@/components/railwatch/segmented"
5
- import { Button } from "@/components/ui/button"
6
- import { Input } from "@/components/ui/input"
7
- import { Label } from "@/components/ui/label"
8
- import {
9
- Popover,
10
- PopoverContent,
11
- PopoverTrigger,
12
- } from "@/components/ui/popover"
13
- import { WINDOWS, useWindow } from "@/hooks/use-window"
14
-
15
- function toLocalInputValue(iso: string) {
16
- const d = new Date(iso)
17
- const pad = (n: number) => String(n).padStart(2, "0")
18
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`
19
- }
20
-
21
- function startOfDay(d: Date) {
22
- const x = new Date(d)
23
- x.setHours(0, 0, 0, 0)
24
- return x
25
- }
26
-
27
- const PRESETS: { label: string; range: () => [Date, Date] }[] = [
28
- { label: "Today", range: () => [startOfDay(new Date()), new Date()] },
29
- {
30
- label: "Yesterday",
31
- range: () => {
32
- const end = startOfDay(new Date())
33
- const start = startOfDay(new Date(end.getTime() - 24 * 60 * 60 * 1000))
34
- return [start, end]
35
- },
36
- },
37
- {
38
- label: "This week",
39
- range: () => {
40
- const start = startOfDay(new Date())
41
- start.setDate(start.getDate() - start.getDay())
42
- return [start, new Date()]
43
- },
44
- },
45
- ]
46
-
47
- // Fixed 1h–30d toggle plus a "Custom" popover with quick presets and
48
- // from/to datetime-local inputs; both write through useWindow().
49
- export function WindowPicker() {
50
- const { window, from, to, set, setRange, label } = useWindow()
51
- const [open, setOpen] = useState(false)
52
- const [fromInput, setFromInput] = useState(() =>
53
- toLocalInputValue(
54
- from ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),
55
- ),
56
- )
57
- const [toInput, setToInput] = useState(() =>
58
- toLocalInputValue(to ?? new Date().toISOString()),
59
- )
60
-
61
- const applyRange = (f: Date, t: Date) => {
62
- setFromInput(toLocalInputValue(f.toISOString()))
63
- setToInput(toLocalInputValue(t.toISOString()))
64
- setRange(f.toISOString(), t.toISOString())
65
- setOpen(false)
66
- }
67
-
68
- const applyInputs = () => {
69
- const f = new Date(fromInput)
70
- const t = new Date(toInput)
71
- if (!Number.isNaN(f.getTime()) && !Number.isNaN(t.getTime()))
72
- applyRange(f, t)
73
- }
74
-
75
- return (
76
- <Segmented>
77
- {WINDOWS.map((w) => (
78
- <SegmentedItem
79
- key={w.value}
80
- active={window === w.value}
81
- onClick={() => set(w.value)}
82
- aria-label={w.label}
83
- >
84
- {w.value}
85
- </SegmentedItem>
86
- ))}
87
- <Popover open={open} onOpenChange={setOpen}>
88
- <PopoverTrigger asChild>
89
- <SegmentedItem
90
- active={window === "custom"}
91
- onClick={() => setOpen(true)}
92
- aria-label="Custom range"
93
- className="px-2"
94
- >
95
- <CalendarClock className="size-3" />
96
- <span className="hidden sm:inline">
97
- {window === "custom" ? label : "Custom"}
98
- </span>
99
- </SegmentedItem>
100
- </PopoverTrigger>
101
- <PopoverContent className="w-72" align="end">
102
- <div className="flex flex-col gap-3">
103
- <div className="flex flex-wrap gap-1">
104
- {PRESETS.map((preset) => (
105
- <Button
106
- key={preset.label}
107
- type="button"
108
- variant="outline"
109
- size="sm"
110
- onClick={() => {
111
- const [f, t] = preset.range()
112
- applyRange(f, t)
113
- }}
114
- >
115
- {preset.label}
116
- </Button>
117
- ))}
118
- </div>
119
- <div className="flex flex-col gap-2">
120
- <Label htmlFor="window-picker-from" className="text-xs">
121
- From
122
- </Label>
123
- <Input
124
- id="window-picker-from"
125
- type="datetime-local"
126
- value={fromInput}
127
- onChange={(e) => setFromInput(e.target.value)}
128
- />
129
- <Label htmlFor="window-picker-to" className="text-xs">
130
- To
131
- </Label>
132
- <Input
133
- id="window-picker-to"
134
- type="datetime-local"
135
- value={toInput}
136
- onChange={(e) => setToInput(e.target.value)}
137
- />
138
- </div>
139
- <Button type="button" size="sm" onClick={applyInputs}>
140
- Apply
141
- </Button>
142
- </div>
143
- </PopoverContent>
144
- </Popover>
145
- </Segmented>
146
- )
147
- }
@@ -1,51 +0,0 @@
1
- import * as AvatarPrimitive from "@radix-ui/react-avatar"
2
- import type * as React from "react"
3
-
4
- import { cn } from "@/lib/utils"
5
-
6
- function Avatar({
7
- className,
8
- ...props
9
- }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
10
- return (
11
- <AvatarPrimitive.Root
12
- data-slot="avatar"
13
- className={cn(
14
- "relative flex size-8 shrink-0 overflow-hidden rounded-full",
15
- className,
16
- )}
17
- {...props}
18
- />
19
- )
20
- }
21
-
22
- function AvatarImage({
23
- className,
24
- ...props
25
- }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
26
- return (
27
- <AvatarPrimitive.Image
28
- data-slot="avatar-image"
29
- className={cn("aspect-square size-full", className)}
30
- {...props}
31
- />
32
- )
33
- }
34
-
35
- function AvatarFallback({
36
- className,
37
- ...props
38
- }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
39
- return (
40
- <AvatarPrimitive.Fallback
41
- data-slot="avatar-fallback"
42
- className={cn(
43
- "bg-muted flex size-full items-center justify-center rounded-full",
44
- className,
45
- )}
46
- {...props}
47
- />
48
- )
49
- }
50
-
51
- export { Avatar, AvatarFallback, AvatarImage }
@@ -1,46 +0,0 @@
1
- import { Slot } from "@radix-ui/react-slot"
2
- import { type VariantProps, cva } from "class-variance-authority"
3
- import type * as React from "react"
4
-
5
- import { cn } from "@/lib/utils"
6
-
7
- const badgeVariants = cva(
8
- "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
9
- {
10
- variants: {
11
- variant: {
12
- default:
13
- "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
14
- secondary:
15
- "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
16
- destructive:
17
- "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
18
- outline:
19
- "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
20
- },
21
- },
22
- defaultVariants: {
23
- variant: "default",
24
- },
25
- },
26
- )
27
-
28
- function Badge({
29
- className,
30
- variant,
31
- asChild = false,
32
- ...props
33
- }: React.ComponentProps<"span"> &
34
- VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
35
- const Comp = asChild ? Slot : "span"
36
-
37
- return (
38
- <Comp
39
- data-slot="badge"
40
- className={cn(badgeVariants({ variant }), className)}
41
- {...props}
42
- />
43
- )
44
- }
45
-
46
- export { Badge, badgeVariants }