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,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 }