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,64 +0,0 @@
1
- import type { ReactNode } from "react"
2
-
3
- import { cn } from "@/lib/utils"
4
-
5
- // Nightwatch's period bar: a bordered pill holding mono buttons, the selected
6
- // one filled blue. Used for time windows, percentiles, and status tabs so
7
- // every segmented choice in the app looks the same.
8
- export function Segmented({
9
- children,
10
- className,
11
- }: {
12
- children: ReactNode
13
- className?: string
14
- }) {
15
- return (
16
- <div
17
- className={cn(
18
- "bg-card flex h-8 shrink-0 items-center gap-px rounded-lg border p-0.5",
19
- className,
20
- )}
21
- >
22
- {children}
23
- </div>
24
- )
25
- }
26
-
27
- export function SegmentedItem({
28
- active,
29
- onClick,
30
- children,
31
- className,
32
- mono = true,
33
- disabled,
34
- "aria-label": ariaLabel,
35
- }: {
36
- active: boolean
37
- onClick: () => void
38
- children: ReactNode
39
- className?: string
40
- mono?: boolean
41
- disabled?: boolean
42
- "aria-label"?: string
43
- }) {
44
- return (
45
- <button
46
- type="button"
47
- onClick={onClick}
48
- disabled={disabled}
49
- aria-pressed={active}
50
- aria-label={ariaLabel}
51
- className={cn(
52
- "flex h-full min-w-8 items-center justify-center gap-1.5 rounded-md border px-2.5 text-xs whitespace-nowrap transition-colors",
53
- mono && "font-mono uppercase",
54
- active
55
- ? "bg-primary border-primary text-primary-foreground"
56
- : "text-muted-foreground hover:bg-accent hover:text-foreground border-transparent",
57
- disabled && "cursor-not-allowed opacity-50",
58
- className,
59
- )}
60
- >
61
- {children}
62
- </button>
63
- )
64
- }
@@ -1,400 +0,0 @@
1
- import {
2
- Area,
3
- AreaChart,
4
- Bar,
5
- BarChart,
6
- CartesianGrid,
7
- Line,
8
- LineChart,
9
- ReferenceLine,
10
- XAxis,
11
- YAxis,
12
- } from "recharts"
13
-
14
- import { useChartHover } from "@/components/railwatch/chart-hover"
15
- import {
16
- type ChartConfig,
17
- ChartContainer,
18
- ChartTooltip,
19
- ChartTooltipContent,
20
- } from "@/components/ui/chart"
21
- import { ms } from "@/lib/format"
22
- import type {
23
- DeployMarker,
24
- Percentile,
25
- SeriesPoint,
26
- SessionStatusPoint,
27
- } from "@/types"
28
-
29
- // Status vocabulary, same as the badges: ok is quiet grey so the amber and
30
- // red slices are what the eye lands on; latency lines use the accent for
31
- // the chosen percentile and a muted amber for the average.
32
- const config = {
33
- count: { label: "OK", color: "var(--ok)" },
34
- errors: { label: "5xx", color: "var(--danger)" },
35
- client_errors: { label: "4xx", color: "var(--warning)" },
36
- p50: { label: "p50", color: "var(--primary)" },
37
- p95: { label: "p95", color: "var(--primary)" },
38
- p99: { label: "p99", color: "var(--primary)" },
39
- avg: { label: "avg", color: "var(--warning)" },
40
- } satisfies ChartConfig
41
-
42
- // Bucket width in ms, read off the data so the axis needs no page context:
43
- // a minute chart labels ticks by time, a day chart by date.
44
- function bucketWidth(data: { t: string }[]) {
45
- if (data.length < 2) return 0
46
- return new Date(data[1].t).getTime() - new Date(data[0].t).getTime()
47
- }
48
-
49
- const DAY = 24 * 60 * 60 * 1000
50
-
51
- function tickFormatter(data: { t: string }[]) {
52
- const width = bucketWidth(data)
53
- const span =
54
- data.length > 1
55
- ? new Date(data[data.length - 1].t).getTime() -
56
- new Date(data[0].t).getTime()
57
- : 0
58
- if (width >= DAY || span > 2 * DAY)
59
- return (t: string) =>
60
- new Date(t).toLocaleDateString(undefined, {
61
- month: "short",
62
- day: "numeric",
63
- })
64
- return (t: string) =>
65
- new Date(t).toLocaleTimeString(undefined, {
66
- hour: "2-digit",
67
- minute: "2-digit",
68
- })
69
- }
70
-
71
- function stamp(t: string) {
72
- const d = new Date(t)
73
- return d.toLocaleString(undefined, {
74
- day: "2-digit",
75
- month: "short",
76
- hour: "2-digit",
77
- minute: "2-digit",
78
- })
79
- }
80
-
81
- const axisTick = { fontSize: 10, fontFamily: "var(--font-mono)" }
82
-
83
- // A line only joins neighbouring points, so a bucket with data between two
84
- // quiet ones (common at a minute's width) would draw nothing. Mark those
85
- // lone points with a dot; everything else stays a plain line. Recharts can
86
- // call this with an index past the array it was built over (a live reload
87
- // swapping in a shorter series mid-render did, LC-43), so neighbours are
88
- // looked up defensively rather than assumed.
89
- export function loneDot(
90
- data: Record<string, unknown>[],
91
- key: string,
92
- color: string,
93
- ) {
94
- const LoneDot = (props: { cx?: number; cy?: number; index?: number }) => {
95
- const { cx, cy, index } = props
96
- if (cx == null || cy == null || index == null) return <g key={index} />
97
- const before = data[index - 1]?.[key] ?? null
98
- const after = data[index + 1]?.[key] ?? null
99
- if (before != null || after != null) return <g key={index} />
100
- return <circle key={index} cx={cx} cy={cy} r={2} fill={color} />
101
- }
102
- return LoneDot
103
- }
104
-
105
- function markers(deploys: DeployMarker[] = []) {
106
- return deploys.map((d) => (
107
- <ReferenceLine
108
- key={d.deploy}
109
- x={new Date(d.at).toISOString()}
110
- stroke="var(--primary)"
111
- strokeDasharray="3 3"
112
- label={{
113
- value: d.ref,
114
- position: "insideTopRight",
115
- fontSize: 9,
116
- fill: "var(--primary)",
117
- fontFamily: "var(--font-mono)",
118
- }}
119
- />
120
- ))
121
- }
122
-
123
- // Recharts calls onMouseMove with the hovered point's x value as
124
- // `activeLabel` (our bucket ISO timestamp) — feed that into ChartHoverProvider
125
- // so a table elsewhere on the page can highlight the matching row.
126
- function useBucketHover() {
127
- const { setHoveredKey } = useChartHover()
128
- return {
129
- onMouseMove: (state: { activeLabel?: string | number }) => {
130
- if (typeof state?.activeLabel === "string")
131
- setHoveredKey(state.activeLabel)
132
- },
133
- onMouseLeave: () => setHoveredKey(null),
134
- }
135
- }
136
-
137
- // Bucket-range caption under a chart, like Nightwatch's "02 Nov 18:00 UTC".
138
- function Range({ data }: { data: { t: string }[] }) {
139
- if (data.length === 0) return null
140
- return (
141
- <div className="text-muted-foreground mt-1 flex justify-between font-mono text-[10px]">
142
- <span>{stamp(data[0].t)}</span>
143
- <span>{stamp(data[data.length - 1].t)}</span>
144
- </div>
145
- )
146
- }
147
-
148
- export function ThroughputChart({
149
- data,
150
- deploys,
151
- label = "Requests",
152
- className = "h-40",
153
- }: {
154
- data: SeriesPoint[]
155
- deploys?: DeployMarker[]
156
- label?: string
157
- className?: string
158
- }) {
159
- const cfg = { ...config, count: { ...config.count, label } }
160
- const hover = useBucketHover()
161
- const tick = tickFormatter(data)
162
- return (
163
- <div>
164
- <ChartContainer config={cfg} className={`${className} w-full`}>
165
- <BarChart
166
- data={data.map((d) => ({
167
- ...d,
168
- t: new Date(d.t).toISOString(),
169
- ok: Math.max(0, d.count - d.errors - d.client_errors),
170
- }))}
171
- barCategoryGap={2}
172
- onMouseMove={hover.onMouseMove}
173
- onMouseLeave={hover.onMouseLeave}
174
- >
175
- <CartesianGrid vertical={false} strokeDasharray="2 4" />
176
- <XAxis
177
- dataKey="t"
178
- tickFormatter={tick}
179
- tickLine={false}
180
- axisLine={false}
181
- minTickGap={40}
182
- tick={axisTick}
183
- />
184
- <YAxis
185
- width={36}
186
- tickLine={false}
187
- axisLine={false}
188
- tick={axisTick}
189
- tickFormatter={(v) => compact(Number(v))}
190
- />
191
- <ChartTooltip
192
- content={
193
- <ChartTooltipContent labelFormatter={(v) => stamp(String(v))} />
194
- }
195
- />
196
- <Bar
197
- dataKey="ok"
198
- name="count"
199
- fill="var(--color-count)"
200
- stackId="a"
201
- />
202
- <Bar
203
- dataKey="client_errors"
204
- fill="var(--color-client_errors)"
205
- stackId="a"
206
- />
207
- <Bar
208
- dataKey="errors"
209
- fill="var(--color-errors)"
210
- stackId="a"
211
- radius={[2, 2, 0, 0]}
212
- />
213
- {markers(deploys)}
214
- </BarChart>
215
- </ChartContainer>
216
- <Range data={data} />
217
- </div>
218
- )
219
- }
220
-
221
- // Release health's own vocabulary: sessions stacked by the worst status
222
- // they reached, so a regression reads as amber and red eating the band.
223
- const sessionConfig = {
224
- ok: { label: "ok", color: "var(--ok)" },
225
- errored: { label: "errored", color: "var(--warning)" },
226
- crashed: { label: "crashed", color: "var(--danger)" },
227
- } satisfies ChartConfig
228
-
229
- export function SessionStatusChart({
230
- data,
231
- deploys,
232
- className = "h-40",
233
- }: {
234
- data: SessionStatusPoint[]
235
- deploys?: DeployMarker[]
236
- className?: string
237
- }) {
238
- const hover = useBucketHover()
239
- const tick = tickFormatter(data)
240
- return (
241
- <div>
242
- <ChartContainer config={sessionConfig} className={`${className} w-full`}>
243
- <AreaChart
244
- data={data.map((d) => ({ ...d, t: new Date(d.t).toISOString() }))}
245
- onMouseMove={hover.onMouseMove}
246
- onMouseLeave={hover.onMouseLeave}
247
- >
248
- <CartesianGrid vertical={false} strokeDasharray="2 4" />
249
- <XAxis
250
- dataKey="t"
251
- tickFormatter={tick}
252
- tickLine={false}
253
- axisLine={false}
254
- minTickGap={40}
255
- tick={axisTick}
256
- />
257
- <YAxis
258
- width={36}
259
- tickLine={false}
260
- axisLine={false}
261
- tick={axisTick}
262
- tickFormatter={(v) => compact(Number(v))}
263
- />
264
- <ChartTooltip
265
- content={
266
- <ChartTooltipContent labelFormatter={(v) => stamp(String(v))} />
267
- }
268
- />
269
- {(["ok", "errored", "crashed"] as const).map((key) => (
270
- <Area
271
- key={key}
272
- dataKey={key}
273
- stackId="sessions"
274
- stroke={`var(--color-${key})`}
275
- fill={`var(--color-${key})`}
276
- fillOpacity={0.3}
277
- strokeWidth={1.25}
278
- isAnimationActive={false}
279
- />
280
- ))}
281
- {markers(deploys)}
282
- </AreaChart>
283
- </ChartContainer>
284
- <Range data={data} />
285
- </div>
286
- )
287
- }
288
-
289
- export function LatencyChart({
290
- data,
291
- deploys,
292
- percentile = "p95",
293
- className = "h-40",
294
- }: {
295
- data: SeriesPoint[]
296
- deploys?: DeployMarker[]
297
- percentile?: Percentile
298
- className?: string
299
- }) {
300
- const hover = useBucketHover()
301
- const tick = tickFormatter(data)
302
- const points = data.map((d) => ({ ...d, t: new Date(d.t).toISOString() }))
303
- return (
304
- <div>
305
- <ChartContainer config={config} className={`${className} w-full`}>
306
- <LineChart
307
- data={points}
308
- onMouseMove={hover.onMouseMove}
309
- onMouseLeave={hover.onMouseLeave}
310
- >
311
- <CartesianGrid vertical={false} strokeDasharray="2 4" />
312
- <XAxis
313
- dataKey="t"
314
- tickFormatter={tick}
315
- tickLine={false}
316
- axisLine={false}
317
- minTickGap={40}
318
- tick={axisTick}
319
- />
320
- <YAxis
321
- width={44}
322
- tickLine={false}
323
- axisLine={false}
324
- tick={axisTick}
325
- tickFormatter={(v) => ms(Number(v), 0)}
326
- />
327
- <ChartTooltip
328
- content={
329
- <ChartTooltipContent
330
- labelFormatter={(v) => stamp(String(v))}
331
- formatter={(value, name) => [
332
- value == null ? "—" : ms(Number(value)),
333
- String(name),
334
- ]}
335
- />
336
- }
337
- />
338
- <Line
339
- dataKey={percentile}
340
- stroke={`var(--color-${percentile})`}
341
- dot={loneDot(points, percentile, `var(--color-${percentile})`)}
342
- strokeWidth={1.75}
343
- isAnimationActive={false}
344
- />
345
- <Line
346
- dataKey="avg"
347
- stroke="var(--color-avg)"
348
- dot={loneDot(points, "avg", "var(--color-avg)")}
349
- strokeWidth={1.25}
350
- strokeOpacity={0.8}
351
- isAnimationActive={false}
352
- />
353
- {markers(deploys)}
354
- </LineChart>
355
- </ChartContainer>
356
- <Range data={data} />
357
- </div>
358
- )
359
- }
360
-
361
- export function Sparkline({
362
- data,
363
- className = "h-10 w-32",
364
- hoverKey,
365
- }: {
366
- data: number[]
367
- className?: string
368
- hoverKey?: string
369
- }) {
370
- const { hoveredKey, setHoveredKey } = useChartHover()
371
- const active = hoverKey != null && hoveredKey === hoverKey
372
- return (
373
- <ChartContainer config={config} className={className}>
374
- {/* recharts pads every side by 5px by default, which leaves a table
375
- cell's 10px-tall sparkline no plot area at all; the stroke's
376
- half-width is the only margin a chart this small needs. */}
377
- <AreaChart
378
- data={data.map((v) => ({ v }))}
379
- margin={{ top: 1, right: 1, bottom: 1, left: 1 }}
380
- onMouseEnter={hoverKey ? () => setHoveredKey(hoverKey) : undefined}
381
- onMouseLeave={hoverKey ? () => setHoveredKey(null) : undefined}
382
- >
383
- <Area
384
- dataKey="v"
385
- stroke={active ? "var(--primary)" : "var(--muted-foreground)"}
386
- fill={active ? "var(--primary)" : "var(--muted-foreground)"}
387
- fillOpacity={active ? 0.35 : 0.15}
388
- strokeWidth={active ? 2 : 1.25}
389
- isAnimationActive={false}
390
- />
391
- </AreaChart>
392
- </ChartContainer>
393
- )
394
- }
395
-
396
- function compact(n: number) {
397
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
398
- if (n >= 1_000) return `${(n / 1_000).toFixed(n >= 10_000 ? 0 : 1)}k`
399
- return String(n)
400
- }
@@ -1,89 +0,0 @@
1
- import {
2
- Dialog,
3
- DialogContent,
4
- DialogDescription,
5
- DialogHeader,
6
- DialogTitle,
7
- } from "@/components/ui/dialog"
8
-
9
- interface Shortcut {
10
- keys: string[]
11
- description: string
12
- }
13
-
14
- const GROUPS: { title: string; shortcuts: Shortcut[] }[] = [
15
- {
16
- title: "Navigation",
17
- shortcuts: [
18
- { keys: ["⌘", "K"], description: "Open command palette" },
19
- { keys: ["g", "d"], description: "Go to dashboard" },
20
- { keys: ["g", "o"], description: "Go to Overview" },
21
- { keys: ["g", "r"], description: "Go to Requests" },
22
- { keys: ["g", "j"], description: "Go to Jobs" },
23
- { keys: ["g", "q"], description: "Go to Queries" },
24
- { keys: ["g", "i"], description: "Go to Issues" },
25
- { keys: ["?"], description: "Show this help" },
26
- ],
27
- },
28
- {
29
- title: "Lists (requests, jobs, queries, issues, logs, …)",
30
- shortcuts: [
31
- { keys: ["j"], description: "Move highlight down" },
32
- { keys: ["k"], description: "Move highlight up" },
33
- { keys: ["↵"], description: "Open highlighted row" },
34
- { keys: ["o"], description: "Open highlighted row in a new tab" },
35
- { keys: ["Esc"], description: "Clear highlight" },
36
- { keys: ["/"], description: "Focus the filter bar" },
37
- ],
38
- },
39
- ]
40
-
41
- export function ShortcutsDialog({
42
- open,
43
- onOpenChange,
44
- }: {
45
- open: boolean
46
- onOpenChange: (open: boolean) => void
47
- }) {
48
- return (
49
- <Dialog open={open} onOpenChange={onOpenChange}>
50
- <DialogContent className="sm:max-w-sm">
51
- <DialogHeader>
52
- <DialogTitle>Keyboard shortcuts</DialogTitle>
53
- <DialogDescription>Available anywhere in the app.</DialogDescription>
54
- </DialogHeader>
55
- <div className="flex flex-col gap-4">
56
- {GROUPS.map((group) => (
57
- <div key={group.title} className="flex flex-col gap-2">
58
- <div className="text-muted-foreground text-xs font-medium tracking-wide uppercase">
59
- {group.title}
60
- </div>
61
- <ul className="flex flex-col gap-2">
62
- {group.shortcuts.map((shortcut) => (
63
- <li
64
- key={shortcut.description}
65
- className="flex items-center justify-between text-sm"
66
- >
67
- <span className="text-muted-foreground">
68
- {shortcut.description}
69
- </span>
70
- <span className="flex items-center gap-1">
71
- {shortcut.keys.map((key, i) => (
72
- <kbd
73
- key={i}
74
- className="bg-muted rounded border px-1.5 py-0.5 font-mono text-xs"
75
- >
76
- {key}
77
- </kbd>
78
- ))}
79
- </span>
80
- </li>
81
- ))}
82
- </ul>
83
- </div>
84
- ))}
85
- </div>
86
- </DialogContent>
87
- </Dialog>
88
- )
89
- }
@@ -1,121 +0,0 @@
1
- import { X } from "lucide-react"
2
- import { toast } from "sonner"
3
-
4
- import { cn } from "@/lib/utils"
5
-
6
- // Notices spoken in the signal vocabulary the rest of the UI uses. The
7
- // aspect carries the meaning, so there is no icon to interpret: green is
8
- // clear (done, saved, sent), amber is caution (something to look at, still
9
- // safe), red is stop (this failed). The head sits at the left of the card
10
- // like a lineside signal; the lit lamp is the only colour on the card.
11
- export type ToastAspect = "clear" | "caution" | "stop"
12
-
13
- const LAMP_CLASS: Record<ToastAspect, string> = {
14
- stop: "bg-danger",
15
- caution: "bg-warning",
16
- clear: "bg-live",
17
- }
18
- const LAMP_ORDER: ToastAspect[] = ["stop", "caution", "clear"]
19
-
20
- // Clear notices leave on their own; the line is clear and there is nothing
21
- // to act on. Caution and stop wait to be read.
22
- const DURATION: Record<ToastAspect, number> = {
23
- clear: 4000,
24
- caution: 8000,
25
- stop: Infinity,
26
- }
27
-
28
- function Head({ aspect }: { aspect: ToastAspect }) {
29
- return (
30
- <span
31
- aria-hidden
32
- className="border-foreground/30 bg-background/60 inline-flex w-3 shrink-0 flex-col items-center justify-between rounded-full border px-px py-1"
33
- style={{ height: 30 }}
34
- >
35
- {LAMP_ORDER.map((lamp) => (
36
- <span key={lamp} className="relative inline-flex size-2">
37
- {lamp === aspect && (
38
- <span
39
- className={cn(
40
- "absolute inline-flex h-full w-full rounded-full opacity-40 blur-[2px]",
41
- LAMP_CLASS[lamp],
42
- )}
43
- />
44
- )}
45
- <span
46
- className={cn(
47
- "relative inline-flex size-2 rounded-full",
48
- lamp === aspect ? LAMP_CLASS[lamp] : "bg-neutral-400/15",
49
- )}
50
- />
51
- </span>
52
- ))}
53
- </span>
54
- )
55
- }
56
-
57
- // The card itself. `onDismiss` is what the X does; the marketing hero
58
- // renders one with no dismiss as a static example.
59
- export function SignalCard({
60
- aspect,
61
- title,
62
- description,
63
- onDismiss,
64
- className,
65
- }: {
66
- aspect: ToastAspect
67
- title: string
68
- description?: string
69
- onDismiss?: () => void
70
- className?: string
71
- }) {
72
- const role = aspect === "stop" ? "alert" : "status"
73
- return (
74
- <div
75
- role={onDismiss ? role : undefined}
76
- data-aspect={aspect}
77
- className={cn(
78
- "bg-popover text-popover-foreground flex w-full items-start gap-3 rounded-lg border px-3.5 py-3 shadow-lg md:w-[360px]",
79
- className,
80
- )}
81
- >
82
- <Head aspect={aspect} />
83
- <div className="min-w-0 flex-1 pt-0.5">
84
- <p className="text-sm leading-snug font-medium">{title}</p>
85
- {description && (
86
- <p className="text-muted-foreground mt-0.5 text-xs leading-relaxed">
87
- {description}
88
- </p>
89
- )}
90
- </div>
91
- {onDismiss && (
92
- <button
93
- type="button"
94
- onClick={onDismiss}
95
- aria-label="Dismiss"
96
- className="text-muted-foreground hover:text-foreground -mt-0.5 -mr-1 rounded p-1"
97
- >
98
- <X className="size-3.5" />
99
- </button>
100
- )}
101
- </div>
102
- )
103
- }
104
-
105
- export function signalToast(
106
- aspect: ToastAspect,
107
- title: string,
108
- description?: string,
109
- ) {
110
- return toast.custom(
111
- (id) => (
112
- <SignalCard
113
- aspect={aspect}
114
- title={title}
115
- description={description}
116
- onDismiss={() => toast.dismiss(id)}
117
- />
118
- ),
119
- { duration: DURATION[aspect], unstyled: true },
120
- )
121
- }
@@ -1,37 +0,0 @@
1
- import { ArrowDown, ArrowUp } from "lucide-react"
2
-
3
- import { cn } from "@/lib/utils"
4
-
5
- // Clickable DataTable column header for a sortable numeric column. Dumb by
6
- // design: the page builds the sort/dir URL with its own route helper and
7
- // passes the click handler in.
8
- export function SortHeader({
9
- label,
10
- active,
11
- dir,
12
- onClick,
13
- }: {
14
- label: string
15
- active: boolean
16
- dir: "asc" | "desc"
17
- onClick: () => void
18
- }) {
19
- return (
20
- <button
21
- type="button"
22
- onClick={onClick}
23
- className={cn(
24
- "hover:text-foreground inline-flex items-center gap-1",
25
- active && "text-foreground font-semibold",
26
- )}
27
- >
28
- {label}
29
- {active &&
30
- (dir === "asc" ? (
31
- <ArrowUp className="size-3" />
32
- ) : (
33
- <ArrowDown className="size-3" />
34
- ))}
35
- </button>
36
- )
37
- }