railwatch 0.9.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +37 -33
  3. data/CHANGELOG.md +66 -0
  4. data/README.md +51 -60
  5. data/config/routes.rb +0 -59
  6. data/docs/ai-and-mcp.md +9 -8
  7. data/docs/configuration.md +57 -71
  8. data/docs/faq.md +17 -25
  9. data/docs/getting-started.md +105 -117
  10. data/docs/replacing-nightwatch.md +8 -7
  11. data/docs/replacing-sentry.md +12 -16
  12. data/docs/security.md +14 -20
  13. data/docs/self-hosting.md +24 -22
  14. data/docs/testing.md +6 -14
  15. data/docs/troubleshooting.md +12 -46
  16. data/lib/generators/railwatch/install/install_generator.rb +12 -240
  17. data/lib/generators/railwatch/install/templates/initializer.rb.tt +10 -40
  18. data/lib/generators/railwatch/install/templates/post-deploy +0 -7
  19. data/lib/railwatch/authentication.rb +6 -5
  20. data/lib/railwatch/buffer.rb +9 -0
  21. data/lib/railwatch/configuration.rb +21 -151
  22. data/lib/railwatch/engine.rb +9 -118
  23. data/lib/railwatch/patches.rb +0 -11
  24. data/lib/railwatch/reporter.rb +92 -21
  25. data/lib/railwatch/sampler.rb +10 -2
  26. data/lib/railwatch/subscribers/process_info.rb +1 -2
  27. data/lib/railwatch/version.rb +1 -1
  28. data/lib/railwatch.rb +8 -64
  29. data/lib/tasks/railwatch_tasks.rake +3 -325
  30. data/llms.txt +23 -25
  31. metadata +3 -453
  32. data/app/channels/railwatch/environment_channel.rb +0 -62
  33. data/app/controllers/concerns/railwatch/telemetry_identity.rb +0 -25
  34. data/app/controllers/railwatch/alerts_controller.rb +0 -48
  35. data/app/controllers/railwatch/anomaly_rules_controller.rb +0 -31
  36. data/app/controllers/railwatch/attachments_controller.rb +0 -22
  37. data/app/controllers/railwatch/broadcasts_controller.rb +0 -15
  38. data/app/controllers/railwatch/cache_events_controller.rb +0 -30
  39. data/app/controllers/railwatch/commands_controller.rb +0 -16
  40. data/app/controllers/railwatch/comments_controller.rb +0 -11
  41. data/app/controllers/railwatch/dashboard_controller.rb +0 -70
  42. data/app/controllers/railwatch/deploys_controller.rb +0 -34
  43. data/app/controllers/railwatch/deprecations_controller.rb +0 -54
  44. data/app/controllers/railwatch/environment_scoped.rb +0 -81
  45. data/app/controllers/railwatch/exceptions_controller.rb +0 -52
  46. data/app/controllers/railwatch/executions_controller.rb +0 -13
  47. data/app/controllers/railwatch/issues_controller.rb +0 -258
  48. data/app/controllers/railwatch/jobs_controller.rb +0 -63
  49. data/app/controllers/railwatch/llm_calls_controller.rb +0 -124
  50. data/app/controllers/railwatch/logs_controller.rb +0 -40
  51. data/app/controllers/railwatch/mails_controller.rb +0 -19
  52. data/app/controllers/railwatch/notifications_controller.rb +0 -11
  53. data/app/controllers/railwatch/outgoing_requests_controller.rb +0 -19
  54. data/app/controllers/railwatch/overview_controller.rb +0 -38
  55. data/app/controllers/railwatch/people_controller.rb +0 -25
  56. data/app/controllers/railwatch/processes_controller.rb +0 -33
  57. data/app/controllers/railwatch/profiles_controller.rb +0 -65
  58. data/app/controllers/railwatch/queries_controller.rb +0 -59
  59. data/app/controllers/railwatch/releases_controller.rb +0 -75
  60. data/app/controllers/railwatch/requests_controller.rb +0 -37
  61. data/app/controllers/railwatch/saved_views_controller.rb +0 -53
  62. data/app/controllers/railwatch/scheduled_tasks_controller.rb +0 -45
  63. data/app/controllers/railwatch/spans_controller.rb +0 -47
  64. data/app/controllers/railwatch/storage_ops_controller.rb +0 -15
  65. data/app/controllers/railwatch/tenants_controller.rb +0 -44
  66. data/app/controllers/railwatch/thresholds_controller.rb +0 -47
  67. data/app/controllers/railwatch/traces_controller.rb +0 -72
  68. data/app/controllers/railwatch/transactions_controller.rb +0 -21
  69. data/app/controllers/railwatch/view_renders_controller.rb +0 -28
  70. data/app/controllers/railwatch/visits_controller.rb +0 -53
  71. data/app/frontend/components/app-content.tsx +0 -26
  72. data/app/frontend/components/app-logo-icon.tsx +0 -23
  73. data/app/frontend/components/app-logo.tsx +0 -14
  74. data/app/frontend/components/app-shell.tsx +0 -38
  75. data/app/frontend/components/app-sidebar-header.tsx +0 -54
  76. data/app/frontend/components/app-sidebar.tsx +0 -94
  77. data/app/frontend/components/app-wordmark.tsx +0 -29
  78. data/app/frontend/components/breadcrumbs.tsx +0 -46
  79. data/app/frontend/components/icon.tsx +0 -16
  80. data/app/frontend/components/input-error.tsx +0 -18
  81. data/app/frontend/components/nav-footer.tsx +0 -46
  82. data/app/frontend/components/nav-main.tsx +0 -41
  83. data/app/frontend/components/nav-user.tsx +0 -50
  84. data/app/frontend/components/railwatch/chart-hover.tsx +0 -34
  85. data/app/frontend/components/railwatch/chart-panel.tsx +0 -170
  86. data/app/frontend/components/railwatch/code.tsx +0 -28
  87. data/app/frontend/components/railwatch/command-palette.tsx +0 -132
  88. data/app/frontend/components/railwatch/copy-block.tsx +0 -52
  89. data/app/frontend/components/railwatch/copy-id.tsx +0 -49
  90. data/app/frontend/components/railwatch/cursor-load-more.tsx +0 -39
  91. data/app/frontend/components/railwatch/data-table.tsx +0 -195
  92. data/app/frontend/components/railwatch/digit-roll.tsx +0 -312
  93. data/app/frontend/components/railwatch/empty-state.tsx +0 -159
  94. data/app/frontend/components/railwatch/env-nav.tsx +0 -130
  95. data/app/frontend/components/railwatch/env-switcher.tsx +0 -145
  96. data/app/frontend/components/railwatch/filter-bar.tsx +0 -153
  97. data/app/frontend/components/railwatch/flamegraph.tsx +0 -324
  98. data/app/frontend/components/railwatch/frames.tsx +0 -295
  99. data/app/frontend/components/railwatch/json-viewer.tsx +0 -159
  100. data/app/frontend/components/railwatch/live-dot.tsx +0 -78
  101. data/app/frontend/components/railwatch/live-toggle.tsx +0 -76
  102. data/app/frontend/components/railwatch/metric-chart.tsx +0 -112
  103. data/app/frontend/components/railwatch/mobile-page-switcher.tsx +0 -89
  104. data/app/frontend/components/railwatch/onboarding.tsx +0 -91
  105. data/app/frontend/components/railwatch/origin-identity.tsx +0 -84
  106. data/app/frontend/components/railwatch/page-header.tsx +0 -42
  107. data/app/frontend/components/railwatch/percentile-picker.tsx +0 -18
  108. data/app/frontend/components/railwatch/relative-time.tsx +0 -18
  109. data/app/frontend/components/railwatch/saved-views.tsx +0 -232
  110. data/app/frontend/components/railwatch/segmented.tsx +0 -64
  111. data/app/frontend/components/railwatch/series-chart.tsx +0 -400
  112. data/app/frontend/components/railwatch/shortcuts-dialog.tsx +0 -89
  113. data/app/frontend/components/railwatch/signal-toast.tsx +0 -121
  114. data/app/frontend/components/railwatch/sort-header.tsx +0 -37
  115. data/app/frontend/components/railwatch/source-link.tsx +0 -57
  116. data/app/frontend/components/railwatch/sparkline-cell.tsx +0 -14
  117. data/app/frontend/components/railwatch/sql-block.tsx +0 -116
  118. data/app/frontend/components/railwatch/stat.tsx +0 -185
  119. data/app/frontend/components/railwatch/status-badge.tsx +0 -79
  120. data/app/frontend/components/railwatch/step-picker.tsx +0 -57
  121. data/app/frontend/components/railwatch/timeline.tsx +0 -405
  122. data/app/frontend/components/railwatch/utilisation-bar.tsx +0 -44
  123. data/app/frontend/components/railwatch/window-picker.tsx +0 -147
  124. data/app/frontend/components/ui/avatar.tsx +0 -51
  125. data/app/frontend/components/ui/badge.tsx +0 -46
  126. data/app/frontend/components/ui/breadcrumb.tsx +0 -109
  127. data/app/frontend/components/ui/button.tsx +0 -60
  128. data/app/frontend/components/ui/card.tsx +0 -92
  129. data/app/frontend/components/ui/chart.tsx +0 -374
  130. data/app/frontend/components/ui/checkbox.tsx +0 -32
  131. data/app/frontend/components/ui/collapsible.tsx +0 -31
  132. data/app/frontend/components/ui/command.tsx +0 -182
  133. data/app/frontend/components/ui/dialog.tsx +0 -158
  134. data/app/frontend/components/ui/dropdown-menu.tsx +0 -255
  135. data/app/frontend/components/ui/hover-card.tsx +0 -42
  136. data/app/frontend/components/ui/input.tsx +0 -21
  137. data/app/frontend/components/ui/label.tsx +0 -24
  138. data/app/frontend/components/ui/popover.tsx +0 -87
  139. data/app/frontend/components/ui/select.tsx +0 -185
  140. data/app/frontend/components/ui/separator.tsx +0 -28
  141. data/app/frontend/components/ui/sheet.tsx +0 -137
  142. data/app/frontend/components/ui/sidebar.tsx +0 -726
  143. data/app/frontend/components/ui/skeleton.tsx +0 -13
  144. data/app/frontend/components/ui/sonner.tsx +0 -41
  145. data/app/frontend/components/ui/table.tsx +0 -114
  146. data/app/frontend/components/ui/tabs.tsx +0 -91
  147. data/app/frontend/components/ui/textarea.tsx +0 -18
  148. data/app/frontend/components/ui/tooltip.tsx +0 -61
  149. data/app/frontend/components/user-info.tsx +0 -32
  150. data/app/frontend/components/user-menu-content.tsx +0 -94
  151. data/app/frontend/entrypoints/application.css +0 -244
  152. data/app/frontend/entrypoints/inertia.tsx +0 -92
  153. data/app/frontend/fonts/CommitMono-Bold.woff2 +0 -0
  154. data/app/frontend/fonts/CommitMono-Regular.woff2 +0 -0
  155. data/app/frontend/fonts/Roboto-Black.woff2 +0 -0
  156. data/app/frontend/fonts/Roboto-Bold.woff2 +0 -0
  157. data/app/frontend/fonts/Roboto-Medium.woff2 +0 -0
  158. data/app/frontend/fonts/Roboto-Regular.woff2 +0 -0
  159. data/app/frontend/hooks/use-appearance.tsx +0 -67
  160. data/app/frontend/hooks/use-clipboard.ts +0 -32
  161. data/app/frontend/hooks/use-flash.tsx +0 -23
  162. data/app/frontend/hooks/use-hotkeys.ts +0 -63
  163. data/app/frontend/hooks/use-initials.tsx +0 -15
  164. data/app/frontend/hooks/use-live.ts +0 -205
  165. data/app/frontend/hooks/use-mobile-navigation.ts +0 -8
  166. data/app/frontend/hooks/use-mobile.ts +0 -21
  167. data/app/frontend/hooks/use-percentile.ts +0 -24
  168. data/app/frontend/hooks/use-reduced-motion.ts +0 -25
  169. data/app/frontend/hooks/use-row-nav.ts +0 -106
  170. data/app/frontend/hooks/use-step.ts +0 -27
  171. data/app/frontend/hooks/use-ticker.ts +0 -12
  172. data/app/frontend/hooks/use-window.ts +0 -58
  173. data/app/frontend/layouts/app/app-sidebar-layout.tsx +0 -107
  174. data/app/frontend/layouts/app-layout.tsx +0 -21
  175. data/app/frontend/layouts/env-layout.tsx +0 -53
  176. data/app/frontend/layouts/persistent-layout.tsx +0 -18
  177. data/app/frontend/lib/cable.ts +0 -10
  178. data/app/frontend/lib/env-pages.ts +0 -236
  179. data/app/frontend/lib/execution-path.ts +0 -55
  180. data/app/frontend/lib/explain.ts +0 -50
  181. data/app/frontend/lib/filter.ts +0 -25
  182. data/app/frontend/lib/flamegraph.ts +0 -155
  183. data/app/frontend/lib/format.ts +0 -71
  184. data/app/frontend/lib/keyboard.ts +0 -18
  185. data/app/frontend/lib/mount.ts +0 -23
  186. data/app/frontend/lib/source-link.ts +0 -217
  187. data/app/frontend/lib/utils.ts +0 -6
  188. data/app/frontend/pages/alerts/index.tsx +0 -171
  189. data/app/frontend/pages/broadcasts/index.tsx +0 -171
  190. data/app/frontend/pages/cache_events/index.tsx +0 -127
  191. data/app/frontend/pages/commands/index.tsx +0 -143
  192. data/app/frontend/pages/deploys/index.tsx +0 -94
  193. data/app/frontend/pages/deploys/show.tsx +0 -309
  194. data/app/frontend/pages/deprecations/index.tsx +0 -176
  195. data/app/frontend/pages/exceptions/index.tsx +0 -271
  196. data/app/frontend/pages/executions/show.tsx +0 -821
  197. data/app/frontend/pages/issues/index.tsx +0 -399
  198. data/app/frontend/pages/issues/show.tsx +0 -1367
  199. data/app/frontend/pages/jobs/index.tsx +0 -381
  200. data/app/frontend/pages/jobs/klass.tsx +0 -215
  201. data/app/frontend/pages/llm_calls/index.tsx +0 -664
  202. data/app/frontend/pages/logs/index.tsx +0 -303
  203. data/app/frontend/pages/mails/index.tsx +0 -218
  204. data/app/frontend/pages/notifications/index.tsx +0 -161
  205. data/app/frontend/pages/outgoing_requests/index.tsx +0 -309
  206. data/app/frontend/pages/overview/show.tsx +0 -333
  207. data/app/frontend/pages/people/index.tsx +0 -102
  208. data/app/frontend/pages/people/show.tsx +0 -149
  209. data/app/frontend/pages/processes/index.tsx +0 -335
  210. data/app/frontend/pages/profiles/index.tsx +0 -110
  211. data/app/frontend/pages/profiles/show.tsx +0 -105
  212. data/app/frontend/pages/queries/index.tsx +0 -492
  213. data/app/frontend/pages/queries/show.tsx +0 -276
  214. data/app/frontend/pages/releases/index.tsx +0 -190
  215. data/app/frontend/pages/releases/release-health.tsx +0 -68
  216. data/app/frontend/pages/releases/release-path.ts +0 -25
  217. data/app/frontend/pages/releases/show.tsx +0 -262
  218. data/app/frontend/pages/requests/index.tsx +0 -235
  219. data/app/frontend/pages/requests/route.tsx +0 -159
  220. data/app/frontend/pages/scheduled_tasks/index.tsx +0 -220
  221. data/app/frontend/pages/spans/index.tsx +0 -229
  222. data/app/frontend/pages/spans/show.tsx +0 -220
  223. data/app/frontend/pages/storage_ops/index.tsx +0 -150
  224. data/app/frontend/pages/tenants/index.tsx +0 -236
  225. data/app/frontend/pages/tenants/show.tsx +0 -477
  226. data/app/frontend/pages/tenants/tenant-path.ts +0 -26
  227. data/app/frontend/pages/thresholds/index.tsx +0 -414
  228. data/app/frontend/pages/traces/show.tsx +0 -208
  229. data/app/frontend/pages/transactions/index.tsx +0 -220
  230. data/app/frontend/pages/view_renders/index.tsx +0 -241
  231. data/app/frontend/pages/visits/index.tsx +0 -321
  232. data/app/frontend/public/icon.png +0 -0
  233. data/app/frontend/public/icon.svg +0 -5
  234. data/app/frontend/routes/index.d.ts +0 -2136
  235. data/app/frontend/routes/index.js +0 -2112
  236. data/app/frontend/types/globals.d.ts +0 -9
  237. data/app/frontend/types/index.ts +0 -288
  238. data/app/frontend/types/vite-env.d.ts +0 -9
  239. data/app/helpers/railwatch/assets_helper.rb +0 -52
  240. data/app/jobs/railwatch/anomaly_scan_job.rb +0 -11
  241. data/app/jobs/railwatch/application_job.rb +0 -7
  242. data/app/jobs/railwatch/auto_resolve_issues_job.rb +0 -19
  243. data/app/jobs/railwatch/check_scheduled_tasks_job.rb +0 -113
  244. data/app/jobs/railwatch/detect_anomalies_job.rb +0 -171
  245. data/app/jobs/railwatch/detect_performance_issues_job.rb +0 -95
  246. data/app/jobs/railwatch/group_exceptions_job.rb +0 -91
  247. data/app/jobs/railwatch/optimize_telemetry_job.rb +0 -23
  248. data/app/jobs/railwatch/performance_scan_job.rb +0 -11
  249. data/app/jobs/railwatch/prune_telemetry_job.rb +0 -129
  250. data/app/jobs/railwatch/release_health_rollup_job.rb +0 -64
  251. data/app/jobs/railwatch/rollup_catchup_job.rb +0 -21
  252. data/app/jobs/railwatch/rollup_job.rb +0 -132
  253. data/app/jobs/railwatch/scheduled_task_scan_job.rb +0 -11
  254. data/app/models/concerns/railwatch/detection_snapshotting.rb +0 -20
  255. data/app/models/railwatch/alert.rb +0 -229
  256. data/app/models/railwatch/alert_rule.rb +0 -121
  257. data/app/models/railwatch/anomaly_rule.rb +0 -51
  258. data/app/models/railwatch/application.rb +0 -44
  259. data/app/models/railwatch/application_record.rb +0 -33
  260. data/app/models/railwatch/comment.rb +0 -36
  261. data/app/models/railwatch/deploy.rb +0 -67
  262. data/app/models/railwatch/environment.rb +0 -68
  263. data/app/models/railwatch/execution_presenter.rb +0 -185
  264. data/app/models/railwatch/filter_query.rb +0 -184
  265. data/app/models/railwatch/followup_receipt.rb +0 -27
  266. data/app/models/railwatch/ingest/batch.rb +0 -355
  267. data/app/models/railwatch/ingest/mapper.rb +0 -585
  268. data/app/models/railwatch/ingest/payload.rb +0 -96
  269. data/app/models/railwatch/ingest/rollup_absorber.rb +0 -170
  270. data/app/models/railwatch/ingest/writer.rb +0 -137
  271. data/app/models/railwatch/issue.rb +0 -277
  272. data/app/models/railwatch/issue_activity.rb +0 -23
  273. data/app/models/railwatch/issue_detection_presenter.rb +0 -309
  274. data/app/models/railwatch/issue_detection_snapshot.rb +0 -77
  275. data/app/models/railwatch/maintenance_task.rb +0 -53
  276. data/app/models/railwatch/saved_view.rb +0 -63
  277. data/app/models/railwatch/telemetry/aggregations.rb +0 -251
  278. data/app/models/railwatch/telemetry/attachment.rb +0 -31
  279. data/app/models/railwatch/telemetry/bounded_gzip.rb +0 -101
  280. data/app/models/railwatch/telemetry/broadcast.rb +0 -13
  281. data/app/models/railwatch/telemetry/cache_event.rb +0 -16
  282. data/app/models/railwatch/telemetry/child.rb +0 -53
  283. data/app/models/railwatch/telemetry/cursor_page.rb +0 -99
  284. data/app/models/railwatch/telemetry/deprecation.rb +0 -13
  285. data/app/models/railwatch/telemetry/enqueued_job.rb +0 -13
  286. data/app/models/railwatch/telemetry/exception.rb +0 -28
  287. data/app/models/railwatch/telemetry/execution.rb +0 -152
  288. data/app/models/railwatch/telemetry/export_delivery.rb +0 -32
  289. data/app/models/railwatch/telemetry/export_destination.rb +0 -66
  290. data/app/models/railwatch/telemetry/health_sample.rb +0 -72
  291. data/app/models/railwatch/telemetry/ingest_batch.rb +0 -31
  292. data/app/models/railwatch/telemetry/llm_call.rb +0 -37
  293. data/app/models/railwatch/telemetry/log.rb +0 -91
  294. data/app/models/railwatch/telemetry/mail.rb +0 -13
  295. data/app/models/railwatch/telemetry/n_plus_one.rb +0 -92
  296. data/app/models/railwatch/telemetry/notification.rb +0 -13
  297. data/app/models/railwatch/telemetry/outgoing_request.rb +0 -13
  298. data/app/models/railwatch/telemetry/person.rb +0 -50
  299. data/app/models/railwatch/telemetry/process.rb +0 -10
  300. data/app/models/railwatch/telemetry/profile.rb +0 -37
  301. data/app/models/railwatch/telemetry/query.rb +0 -57
  302. data/app/models/railwatch/telemetry/query_shape.rb +0 -45
  303. data/app/models/railwatch/telemetry/release_health.rb +0 -69
  304. data/app/models/railwatch/telemetry/rollup.rb +0 -157
  305. data/app/models/railwatch/telemetry/session.rb +0 -24
  306. data/app/models/railwatch/telemetry/span.rb +0 -19
  307. data/app/models/railwatch/telemetry/storage_op.rb +0 -13
  308. data/app/models/railwatch/telemetry/tenant.rb +0 -240
  309. data/app/models/railwatch/telemetry/transaction.rb +0 -13
  310. data/app/models/railwatch/telemetry/view_render.rb +0 -13
  311. data/app/models/railwatch/telemetry/visit.rb +0 -24
  312. data/app/models/railwatch/telemetry_record.rb +0 -107
  313. data/app/models/railwatch/threshold.rb +0 -66
  314. data/app/models/railwatch/user.rb +0 -47
  315. data/app/models/railwatch/viewer.rb +0 -13
  316. data/app/models/railwatch/window.rb +0 -66
  317. data/app/views/layouts/railwatch/dashboard.html.erb +0 -25
  318. data/db/railwatch_migrate/20260916000000_create_railwatch_tables.rb +0 -151
  319. data/db/railwatch_migrate/20260917000000_create_railwatch_maintenance_tasks.rb +0 -18
  320. data/db/railwatch_migrate/20260917120000_create_railwatch_followup_receipts.rb +0 -20
  321. data/db/railwatch_migrate/20260918120000_widen_host_user_ids.rb +0 -71
  322. data/db/railwatch_telemetry_migrate/20260903000000_enable_incremental_vacuum.rb +0 -62
  323. data/db/railwatch_telemetry_migrate/20260903000001_create_telemetry.rb +0 -481
  324. data/db/railwatch_telemetry_migrate/20260903000002_rename_tenant_to_app_tenant.rb +0 -14
  325. data/db/railwatch_telemetry_migrate/20260903000003_add_statement_count_to_transactions.rb +0 -9
  326. data/db/railwatch_telemetry_migrate/20260903000004_add_role_and_channel.rb +0 -10
  327. data/db/railwatch_telemetry_migrate/20260903000005_add_locals_to_exceptions.rb +0 -9
  328. data/db/railwatch_telemetry_migrate/20260903000006_add_spans_health_vitals_and_fts.rb +0 -82
  329. data/db/railwatch_telemetry_migrate/20260903000007_rename_span_attributes_to_payload.rb +0 -10
  330. data/db/railwatch_telemetry_migrate/20260903000008_add_profiles_and_attachments.rb +0 -60
  331. data/db/railwatch_telemetry_migrate/20260903000009_add_truncated_to_attachments.rb +0 -9
  332. data/db/railwatch_telemetry_migrate/20260903000010_create_sessions_and_release_health.rb +0 -51
  333. data/db/railwatch_telemetry_migrate/20260903000011_add_fingerprint_to_exceptions.rb +0 -11
  334. data/db/railwatch_telemetry_migrate/20260904000012_add_failed_to_broadcasts.rb +0 -10
  335. data/db/railwatch_telemetry_migrate/20260904010000_add_filter_cursor_indexes.rb +0 -37
  336. data/db/railwatch_telemetry_migrate/20260904020000_add_n_plus_ones_execution_id_index.rb +0 -11
  337. data/db/railwatch_telemetry_migrate/20260904120000_create_query_shapes.rb +0 -15
  338. data/db/railwatch_telemetry_migrate/20260906120000_add_backpressure_factor_to_ingest_batches.rb +0 -9
  339. data/db/railwatch_telemetry_migrate/20260907000000_rename_lantern_version_on_processes.rb +0 -10
  340. data/db/railwatch_telemetry_migrate/20260913000000_rename_nightrail_version_on_processes.rb +0 -9
  341. data/db/railwatch_telemetry_migrate/20260914000000_drop_orphan_durable_ingest_tables.rb +0 -18
  342. data/db/railwatch_telemetry_migrate/20260914010000_drop_orphan_durable_ingest_columns.rb +0 -25
  343. data/db/railwatch_telemetry_migrate/20260915000000_create_llm_calls.rb +0 -55
  344. data/db/railwatch_telemetry_migrate/20260915120000_add_detail_to_llm_calls.rb +0 -21
  345. data/db/railwatch_telemetry_migrate/20260915200000_add_explained_index_to_queries.rb +0 -16
  346. data/db/railwatch_telemetry_migrate/20260915210000_add_slowest_index_to_queries.rb +0 -18
  347. data/db/railwatch_telemetry_migrate/20260917010000_add_batch_ledger_to_ingest_batches.rb +0 -16
  348. data/db/railwatch_telemetry_migrate/20260919000100_create_export_queue.rb +0 -146
  349. data/db/railwatch_telemetry_migrate/20260922000000_add_covering_indexes_for_dashboard_aggregates.rb +0 -34
  350. data/db/railwatch_telemetry_migrate/20260923000000_add_people_index.rb +0 -15
  351. data/db/railwatch_telemetry_migrate/20260925000000_add_tenant_summary_index.rb +0 -17
  352. data/db/railwatch_telemetry_migrate/20261008000000_add_in_transaction_to_outgoing_requests.rb +0 -10
  353. data/docs/embedded.md +0 -503
  354. data/lib/puma/plugin/railwatch.rb +0 -215
  355. data/lib/railwatch/dashboard_assets.rb +0 -45
  356. data/lib/railwatch/embedded.rb +0 -50
  357. data/lib/railwatch/export/client.rb +0 -124
  358. data/lib/railwatch/export/lease.rb +0 -56
  359. data/lib/railwatch/export/outbox.rb +0 -361
  360. data/lib/railwatch/export/policy.rb +0 -79
  361. data/lib/railwatch/export/sender.rb +0 -130
  362. data/lib/railwatch/ingest_request_body_limit.rb +0 -10
  363. data/lib/railwatch/maintenance.rb +0 -194
  364. data/lib/railwatch/patches/migration_busy_timeout.rb +0 -73
  365. data/lib/railwatch/transport/local.rb +0 -78
  366. data/lib/railwatch/transport/socket.rb +0 -183
  367. data/lib/railwatch/writer.rb +0 -370
  368. data/public/railwatch/assets/CommitMono-Bold-D6h61ieg.woff2 +0 -0
  369. data/public/railwatch/assets/CommitMono-Regular-zr8w7Obm.woff2 +0 -0
  370. data/public/railwatch/assets/Roboto-Black-auA4GeOK.woff2 +0 -0
  371. data/public/railwatch/assets/Roboto-Bold-CJLnO8j1.woff2 +0 -0
  372. data/public/railwatch/assets/Roboto-Medium-Cm2bwKpj.woff2 +0 -0
  373. data/public/railwatch/assets/Roboto-Regular-Chaq1-PV.woff2 +0 -0
  374. data/public/railwatch/assets/app-layout-by7G3sa1.js +0 -41
  375. data/public/railwatch/assets/application-Cc5v_Vk9.css +0 -1
  376. data/public/railwatch/assets/arrow-up-DCZ7emux.js +0 -1
  377. data/public/railwatch/assets/braces-CykT5_5u.js +0 -1
  378. data/public/railwatch/assets/card-C-PPSA0g.js +0 -1
  379. data/public/railwatch/assets/chart-Cov79YC5.js +0 -39
  380. data/public/railwatch/assets/chart-hover-DdK4E-2h.js +0 -1
  381. data/public/railwatch/assets/chart-panel-BPaYRXJB.js +0 -1
  382. data/public/railwatch/assets/code-BYjC4YZ_.js +0 -1
  383. data/public/railwatch/assets/copy-id-LPfoj9cC.js +0 -1
  384. data/public/railwatch/assets/cursor-load-more-DSdhgpGl.js +0 -1
  385. data/public/railwatch/assets/data-table-CQ_YRWQs.js +0 -1
  386. data/public/railwatch/assets/env-layout-CdMWbUkm.js +0 -1
  387. data/public/railwatch/assets/execution-path-7MYt-Qbn.js +0 -1
  388. data/public/railwatch/assets/filter-bar-CrzpQ_vD.js +0 -1
  389. data/public/railwatch/assets/flamegraph-BIFEdTAg.js +0 -2
  390. data/public/railwatch/assets/format-B9SDkrWj.js +0 -1
  391. data/public/railwatch/assets/frames-C5t-x4f0.js +0 -1
  392. data/public/railwatch/assets/index--NQ7us-L.js +0 -1
  393. data/public/railwatch/assets/index-2kgYe_99.js +0 -1
  394. data/public/railwatch/assets/index-4YEPHxtO.js +0 -1
  395. data/public/railwatch/assets/index-AOCC1GDJ.js +0 -1
  396. data/public/railwatch/assets/index-BEo-u0eO.js +0 -1
  397. data/public/railwatch/assets/index-BJ1V3w2N.js +0 -1
  398. data/public/railwatch/assets/index-BPoHsBKb.js +0 -1
  399. data/public/railwatch/assets/index-BZzyGUM3.js +0 -1
  400. data/public/railwatch/assets/index-BgVq7yKL.js +0 -1
  401. data/public/railwatch/assets/index-BqFGEZvW.js +0 -1
  402. data/public/railwatch/assets/index-ByuHJ1CH.js +0 -1
  403. data/public/railwatch/assets/index-C2j50AL2.js +0 -1
  404. data/public/railwatch/assets/index-CLEdTkvW.js +0 -1
  405. data/public/railwatch/assets/index-CWgq8qBu.js +0 -1
  406. data/public/railwatch/assets/index-CXQ7_oVj.js +0 -2
  407. data/public/railwatch/assets/index-CmyYrjS2.js +0 -1
  408. data/public/railwatch/assets/index-Cn9_HjDo.js +0 -1
  409. data/public/railwatch/assets/index-CsMAm9UK.js +0 -1
  410. data/public/railwatch/assets/index-D0Kk_oGY.js +0 -1
  411. data/public/railwatch/assets/index-DC9ZptJh.js +0 -1
  412. data/public/railwatch/assets/index-DNzg7jmV.js +0 -1
  413. data/public/railwatch/assets/index-DQ8uJO5f.js +0 -1
  414. data/public/railwatch/assets/index-Deq14BxB.js +0 -1
  415. data/public/railwatch/assets/index-IaV3Blzp.js +0 -1
  416. data/public/railwatch/assets/index-TYNWcJQs.js +0 -1
  417. data/public/railwatch/assets/index-WonH2fcZ.js +0 -1
  418. data/public/railwatch/assets/index-WxWUaH_d.js +0 -1
  419. data/public/railwatch/assets/index-mUNrl4ky.js +0 -1
  420. data/public/railwatch/assets/inertia-DfNRARuA.js +0 -97
  421. data/public/railwatch/assets/json-viewer-DMsF5glp.js +0 -1
  422. data/public/railwatch/assets/klass-CmHYk5rz.js +0 -1
  423. data/public/railwatch/assets/origin-identity-1R3bqa3R.js +0 -1
  424. data/public/railwatch/assets/percentile-picker-73z5PAps.js +0 -1
  425. data/public/railwatch/assets/relative-time-Dsc_cCdX.js +0 -1
  426. data/public/railwatch/assets/release-health-BV5OaOWd.js +0 -1
  427. data/public/railwatch/assets/route-CWh58C7K.js +0 -1
  428. data/public/railwatch/assets/segmented-DB4zh-Am.js +0 -1
  429. data/public/railwatch/assets/select-DBKjFlOX.js +0 -1
  430. data/public/railwatch/assets/series-chart-BaiDG795.js +0 -1
  431. data/public/railwatch/assets/show-B4ICCFGh.js +0 -1
  432. data/public/railwatch/assets/show-B5lOU9Gv.js +0 -1
  433. data/public/railwatch/assets/show-BYPB74t4.js +0 -2
  434. data/public/railwatch/assets/show-C7A0m2M8.js +0 -1
  435. data/public/railwatch/assets/show-DT5X1zqE.js +0 -1
  436. data/public/railwatch/assets/show-D_NBdXsn.js +0 -2
  437. data/public/railwatch/assets/show-DgPCnRg8.js +0 -1
  438. data/public/railwatch/assets/show-Yl69HF0x.js +0 -1
  439. data/public/railwatch/assets/show-_QxuUQCm.js +0 -3
  440. data/public/railwatch/assets/show-g__jnN36.js +0 -1
  441. data/public/railwatch/assets/show-l4ztfjl9.js +0 -1
  442. data/public/railwatch/assets/sort-header-BcG8ulEH.js +0 -1
  443. data/public/railwatch/assets/source-link-B2183i2-.js +0 -1
  444. data/public/railwatch/assets/sparkline-cell-B47ipscB.js +0 -1
  445. data/public/railwatch/assets/stat-DT2hhsqS.js +0 -1
  446. data/public/railwatch/assets/status-badge-CmbRKlY7.js +0 -1
  447. data/public/railwatch/assets/tenant-path-C4rNGauE.js +0 -1
  448. data/public/railwatch/assets/use-clipboard-oTNeP4ax.js +0 -1
  449. data/public/railwatch/assets/use-live-CLZZj245.js +0 -1
  450. data/public/railwatch/icon.png +0 -0
  451. data/public/railwatch/icon.svg +0 -5
  452. data/public/railwatch/manifest.json +0 -1245
  453. data/public/railwatch/rails-vite.json +0 -1
  454. /data/{app/frontend/lib → lib/generators/railwatch/install/templates}/railwatch.ts +0 -0
@@ -1,664 +0,0 @@
1
- import { Link, router, usePage } from "@inertiajs/react"
2
- import {
3
- ChevronDown,
4
- ChevronRight,
5
- Paperclip,
6
- Scissors,
7
- Sparkles,
8
- } from "lucide-react"
9
- import { Fragment, useState } from "react"
10
-
11
- import { DurationPanel, VolumePanel } from "@/components/railwatch/chart-panel"
12
- import { DataTable } from "@/components/railwatch/data-table"
13
- import { EmptyState } from "@/components/railwatch/empty-state"
14
- import { FilterBar } from "@/components/railwatch/filter-bar"
15
- import { PageHeader } from "@/components/railwatch/page-header"
16
- import { SparklineCell } from "@/components/railwatch/sparkline-cell"
17
- import { StatusBadge } from "@/components/railwatch/status-badge"
18
- import EnvLayout from "@/layouts/env-layout"
19
- import { count, ms, pct, usd, when } from "@/lib/format"
20
- import { cn } from "@/lib/utils"
21
- import * as R from "@/routes"
22
- import type { SeriesPoint, SharedProps } from "@/types"
23
-
24
- interface ModelRow {
25
- group_hash: string
26
- name: string
27
- count: number
28
- errors: number
29
- truncated: number
30
- with_attachments: number
31
- input_tokens: number
32
- output_tokens: number
33
- cache_read_tokens: number
34
- cache_write_tokens: number
35
- cost: number | null
36
- unpriced: number
37
- avg: number
38
- p95: number
39
- sparkline: number[]
40
- }
41
-
42
- interface Recent {
43
- id: number
44
- operation: string
45
- provider: string | null
46
- model: string | null
47
- response_model: string | null
48
- tool_name: string | null
49
- duration: number
50
- status: string | null
51
- error: string | null
52
- streaming: boolean | null
53
- message_count: number | null
54
- tool_count: number | null
55
- input_tokens: number | null
56
- output_tokens: number | null
57
- cache_read_tokens: number | null
58
- cache_write_tokens: number | null
59
- thinking_tokens: number | null
60
- cost: number | null
61
- cost_reported: boolean | null
62
- finish_reason: string | null
63
- provider_request_id: string | null
64
- tools: string | null
65
- attachments: number | null
66
- attachment_types: string | null
67
- attachment_names: string | null
68
- tool_call_id: string | null
69
- params: Record<string, unknown> | null
70
- workflow_id: string | null
71
- workflow_name: string | null
72
- workflow_step_name: string | null
73
- prompt: string | null
74
- completion: string | null
75
- occurred_at: string
76
- execution_id: string | null
77
- execution_preview: string | null
78
- }
79
-
80
- interface ToolRow {
81
- group_hash: string
82
- name: string
83
- count: number
84
- errors: number
85
- avg: number
86
- p95: number
87
- max: number
88
- sparkline: number[]
89
- }
90
-
91
- interface Totals {
92
- count: number
93
- errors: number
94
- tool_count: number
95
- tool_errors: number
96
- input_tokens: number
97
- output_tokens: number
98
- cost: number | null
99
- unpriced: number
100
- }
101
-
102
- interface Props {
103
- models: ModelRow[]
104
- tools: ToolRow[]
105
- recent: Recent[]
106
- totals: Totals
107
- series: SeriesPoint[]
108
- tool_series: SeriesPoint[]
109
- q: string
110
- }
111
-
112
- const NO_MODELS = (
113
- <EmptyState
114
- icon={Sparkles}
115
- title="No model calls in this window"
116
- description="Chats, embeddings, and other model calls your app makes through RubyLLM appear here, with the request or job that made them."
117
- />
118
- )
119
-
120
- const NO_TOOLS = (
121
- <EmptyState
122
- icon={Sparkles}
123
- title="No tool calls in this window"
124
- description="Tools your models invoke appear here, timed separately from the calls that asked for them."
125
- />
126
- )
127
-
128
- // Only present when capture_llm_content is on, which is off by default.
129
- // Same shape as the outgoing-request body viewer: the text is already in
130
- // the row, so opening it fetches nothing.
131
- // max_tokens means the answer was cut off mid-sentence. It is a successful
132
- // HTTP call, so nothing else on this page would ever show it.
133
- function FinishReason({ reason }: { reason: string | null }) {
134
- if (!reason || reason === "stop" || reason === "tool_calls") return null
135
- const bad = reason === "max_tokens" || reason === "content_filter"
136
- return (
137
- <span
138
- className={cn(
139
- "ml-2 inline-flex items-center gap-1 text-xs",
140
- bad ? "text-destructive" : "text-muted-foreground",
141
- )}
142
- title={
143
- reason === "max_tokens"
144
- ? "The answer was cut off at the output token limit"
145
- : `Model stopped: ${reason}`
146
- }
147
- >
148
- <Scissors className="size-3" />
149
- {reason}
150
- </span>
151
- )
152
- }
153
-
154
- function Attachments({ call }: { call: Recent }) {
155
- if (!call.attachments) return null
156
- return (
157
- <span
158
- className="text-muted-foreground ml-2 inline-flex items-center gap-1 text-xs"
159
- title={
160
- call.attachment_names ??
161
- `${call.attachments} file(s): ${call.attachment_types}`
162
- }
163
- >
164
- <Paperclip className="size-3" />
165
- {call.attachment_types ?? call.attachments}
166
- </span>
167
- )
168
- }
169
-
170
- function Content({ call }: { call: Recent }) {
171
- const [open, setOpen] = useState(false)
172
- const { prompt, completion, params, provider_request_id, tools } = call
173
- const hasDetail =
174
- prompt !== null ||
175
- completion !== null ||
176
- provider_request_id !== null ||
177
- tools !== null ||
178
- (params !== null && Object.keys(params).length > 0)
179
- if (!hasDetail) return <span className="text-muted-foreground">–</span>
180
- const Chevron = open ? ChevronDown : ChevronRight
181
- return (
182
- <div className="flex flex-col items-end gap-1">
183
- <button
184
- type="button"
185
- onClick={() => setOpen(!open)}
186
- aria-expanded={open}
187
- className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1"
188
- >
189
- <Chevron className="size-3" />
190
- Detail
191
- </button>
192
- {open && (
193
- <div className="w-[70vw] max-w-[36rem] space-y-2 text-left">
194
- <dl className="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 font-mono text-[10px]">
195
- {provider_request_id && (
196
- <>
197
- <dt className="text-muted-foreground">provider id</dt>
198
- <dd className="break-all">{provider_request_id}</dd>
199
- </>
200
- )}
201
- {tools && (
202
- <>
203
- <dt className="text-muted-foreground">tools</dt>
204
- <dd className="break-all">{tools}</dd>
205
- </>
206
- )}
207
- {params &&
208
- Object.entries(params).map(([k, v]) => (
209
- <Fragment key={k}>
210
- <dt className="text-muted-foreground">{k}</dt>
211
- <dd className="break-all">
212
- {typeof v === "string" || typeof v === "number"
213
- ? v
214
- : JSON.stringify(v)}
215
- </dd>
216
- </Fragment>
217
- ))}
218
- </dl>
219
- {[
220
- ["Prompt", prompt],
221
- ["Completion", completion],
222
- ].map(([label, text]) =>
223
- text ? (
224
- <div key={label}>
225
- <div className="text-muted-foreground text-[10px] tracking-wide uppercase">
226
- {label}
227
- </div>
228
- <pre className="bg-muted max-h-64 overflow-auto rounded p-2 font-mono text-[10px] break-all whitespace-pre-wrap">
229
- {text}
230
- </pre>
231
- </div>
232
- ) : null,
233
- )}
234
- </div>
235
- )}
236
- </div>
237
- )
238
- }
239
-
240
- // A spend figure that leaves calls out is worse than no figure, so say how
241
- // many it leaves out rather than letting the number pass for the whole bill.
242
- function Spend({ cost, unpriced }: { cost: number | null; unpriced: number }) {
243
- if (cost === null)
244
- return unpriced > 0 ? (
245
- <span className="text-muted-foreground" title="No registry pricing">
246
- unpriced
247
- </span>
248
- ) : (
249
- <span className="text-muted-foreground" title="Nothing here has a price">
250
- –
251
- </span>
252
- )
253
- return (
254
- <span className="font-semibold">
255
- {usd(cost)}
256
- {unpriced > 0 && (
257
- <span
258
- className="text-muted-foreground ml-1 text-xs font-normal"
259
- title={`${unpriced} call${unpriced === 1 ? "" : "s"} had no registry pricing and are not in this total`}
260
- >
261
- +{count(unpriced)} unpriced
262
- </span>
263
- )}
264
- </span>
265
- )
266
- }
267
-
268
- function Stat({
269
- label,
270
- children,
271
- }: {
272
- label: string
273
- children: React.ReactNode
274
- }) {
275
- return (
276
- <div className="min-w-0">
277
- <div className="text-muted-foreground text-xs">{label}</div>
278
- <div className="truncate text-lg">{children}</div>
279
- </div>
280
- )
281
- }
282
-
283
- export default function LlmCalls(p: Props) {
284
- const { environment, window } = usePage<SharedProps>().props
285
- const a = environment!.application_id
286
- const e = environment!.id
287
- const href = (r: Recent) =>
288
- r.execution_id
289
- ? R.applicationEnvironmentRequestPath(a, e, r.execution_id)
290
- : null
291
-
292
- return (
293
- <EnvLayout title="LLM">
294
- <PageHeader
295
- title="LLM"
296
- description="Model calls and tool invocations through RubyLLM, by model and by the request or job that made them."
297
- />
298
- <div className="grid grid-cols-2 gap-4 lg:grid-cols-5">
299
- <Stat label="Model calls">{count(p.totals.count)}</Stat>
300
- <Stat label="Tool calls">{count(p.totals.tool_count)}</Stat>
301
- <Stat label="Spend">
302
- <Spend cost={p.totals.cost} unpriced={p.totals.unpriced} />
303
- </Stat>
304
- <Stat label="Tokens in">{count(p.totals.input_tokens)}</Stat>
305
- <Stat label="Tokens out">{count(p.totals.output_tokens)}</Stat>
306
- </div>
307
- <div className="grid gap-4 lg:grid-cols-2">
308
- <VolumePanel
309
- legend="outcome"
310
- label="Model calls"
311
- seriesLabel="Calls"
312
- data={p.series}
313
- />
314
- <DurationPanel label="Model latency" data={p.series} percentile="p95" />
315
- </div>
316
- {p.totals.tool_count > 0 && (
317
- <div className="grid gap-4 lg:grid-cols-2">
318
- <VolumePanel
319
- legend="outcome"
320
- label="Tool calls"
321
- seriesLabel="Calls"
322
- data={p.tool_series}
323
- />
324
- <DurationPanel
325
- label="Tool latency"
326
- data={p.tool_series}
327
- percentile="p95"
328
- />
329
- </div>
330
- )}
331
- <DataTable
332
- rows={p.models}
333
- rowKey={(m) => m.group_hash}
334
- empty={NO_MODELS}
335
- columns={[
336
- {
337
- key: "m",
338
- header: "Model",
339
- cell: (m) => <span className="font-mono text-xs">{m.name}</span>,
340
- },
341
- {
342
- key: "trend",
343
- hideOnMobile: true,
344
- header: "Trend",
345
- cell: (m) => <SparklineCell data={m.sparkline} />,
346
- },
347
- {
348
- key: "n",
349
- header: "Calls",
350
- align: "right",
351
- cell: (m) => count(m.count),
352
- },
353
- {
354
- key: "f",
355
- hideOnMobile: true,
356
- header: "Failed",
357
- align: "right",
358
- cell: (m) => (
359
- <span className={m.errors ? "text-destructive" : ""}>
360
- {pct(m.errors, m.count)}
361
- </span>
362
- ),
363
- },
364
- {
365
- key: "cut",
366
- hideOnMobile: true,
367
- header: "Cut off",
368
- align: "right",
369
- cell: (m) =>
370
- m.truncated ? (
371
- <span
372
- className="text-destructive"
373
- title="Answers stopped at the output token limit"
374
- >
375
- {pct(m.truncated, m.count)}
376
- </span>
377
- ) : (
378
- <span className="text-muted-foreground">–</span>
379
- ),
380
- },
381
- {
382
- key: "att",
383
- hideOnMobile: true,
384
- header: "With files",
385
- align: "right",
386
- cell: (m) =>
387
- m.with_attachments ? (
388
- count(m.with_attachments)
389
- ) : (
390
- <span className="text-muted-foreground">–</span>
391
- ),
392
- },
393
- {
394
- key: "in",
395
- hideOnMobile: true,
396
- header: "Tokens in",
397
- align: "right",
398
- cell: (m) => count(m.input_tokens),
399
- },
400
- {
401
- key: "out",
402
- hideOnMobile: true,
403
- header: "Tokens out",
404
- align: "right",
405
- cell: (m) => count(m.output_tokens),
406
- },
407
- {
408
- key: "cached",
409
- hideOnMobile: true,
410
- header: "Cached",
411
- align: "right",
412
- cell: (m) => count(m.cache_read_tokens),
413
- },
414
- {
415
- key: "p95",
416
- hideOnMobile: true,
417
- header: "p95",
418
- align: "right",
419
- cell: (m) => ms(m.p95),
420
- },
421
- {
422
- key: "cost",
423
- header: "Spend",
424
- align: "right",
425
- cell: (m) => <Spend cost={m.cost} unpriced={m.unpriced} />,
426
- },
427
- ]}
428
- />
429
- {p.tools.length > 0 && (
430
- <>
431
- <h2 className="text-sm font-semibold">Tools</h2>
432
- <DataTable
433
- rows={p.tools}
434
- rowKey={(t) => t.group_hash}
435
- empty={NO_TOOLS}
436
- columns={[
437
- {
438
- key: "t",
439
- header: "Tool",
440
- cell: (t) => (
441
- <span className="font-mono text-xs">{t.name}</span>
442
- ),
443
- },
444
- {
445
- key: "trend",
446
- hideOnMobile: true,
447
- header: "Trend",
448
- cell: (t) => <SparklineCell data={t.sparkline} />,
449
- },
450
- {
451
- key: "n",
452
- header: "Calls",
453
- align: "right",
454
- cell: (t) => count(t.count),
455
- },
456
- {
457
- key: "f",
458
- hideOnMobile: true,
459
- header: "Failed",
460
- align: "right",
461
- cell: (t) => (
462
- <span className={t.errors ? "text-destructive" : ""}>
463
- {pct(t.errors, t.count)}
464
- </span>
465
- ),
466
- },
467
- {
468
- key: "avg",
469
- hideOnMobile: true,
470
- header: "Avg",
471
- align: "right",
472
- cell: (t) => ms(t.avg),
473
- },
474
- {
475
- key: "p95",
476
- header: "p95",
477
- align: "right",
478
- cell: (t) => <span className="font-semibold">{ms(t.p95)}</span>,
479
- },
480
- {
481
- key: "max",
482
- hideOnMobile: true,
483
- header: "Max",
484
- align: "right",
485
- cell: (t) => ms(t.max),
486
- },
487
- ]}
488
- />
489
- </>
490
- )}
491
- <h2 className="text-sm font-semibold">Recent</h2>
492
- <FilterBar
493
- value={p.q}
494
- fields={[
495
- { key: "model", label: "Model" },
496
- { key: "provider", label: "Provider" },
497
- {
498
- key: "operation",
499
- label: "Operation",
500
- options: [
501
- "chat",
502
- "tool",
503
- "embedding",
504
- "image",
505
- "speech",
506
- "transcription",
507
- "moderation",
508
- "rerank",
509
- "ocr",
510
- ],
511
- },
512
- { key: "workflow", label: "Workflow" },
513
- {
514
- key: "finish",
515
- label: "Finish",
516
- options: ["stop", "max_tokens", "tool_calls", "content_filter"],
517
- },
518
- {
519
- key: "attachments",
520
- label: "Files",
521
- options: ["any", "image", "pdf", "audio", "video"],
522
- },
523
- ]}
524
- onChange={(q) =>
525
- router.visit(
526
- R.applicationEnvironmentLlmCallsPath(a, e, {
527
- window,
528
- q: q || undefined,
529
- }),
530
- { preserveState: true },
531
- )
532
- }
533
- placeholder="model:claude-opus-5 operation:chat"
534
- />
535
- <DataTable
536
- rows={p.recent}
537
- rowKey={(r) => r.id}
538
- empty={NO_MODELS}
539
- keyboardNav={{
540
- onOpen: (r, opts) => {
541
- const to = href(r)
542
- if (!to) return
543
- if (opts?.newTab) globalThis.window.open(to, "_blank")
544
- else router.visit(to)
545
- },
546
- }}
547
- columns={[
548
- {
549
- key: "when",
550
- header: "When",
551
- cell: (r) => <span className="text-xs">{when(r.occurred_at)}</span>,
552
- },
553
- {
554
- key: "st",
555
- header: "Status",
556
- cell: (r) => (
557
- <StatusBadge
558
- status={null}
559
- label={r.status === "failed" ? "error" : r.operation}
560
- outcome={r.status === "failed" ? "failed" : undefined}
561
- />
562
- ),
563
- },
564
- {
565
- key: "what",
566
- header: "Call",
567
- grow: true,
568
- cell: (r) => (
569
- <span className="font-mono text-xs">
570
- {r.operation === "tool" ? (
571
- r.tool_name
572
- ) : (
573
- <>
574
- <span className="text-muted-foreground">{r.provider}/</span>
575
- <span className="font-semibold">{r.model}</span>
576
- </>
577
- )}
578
- {r.error && (
579
- <span className="text-destructive ml-2">{r.error}</span>
580
- )}
581
- <FinishReason reason={r.finish_reason} />
582
- <Attachments call={r} />
583
- </span>
584
- ),
585
- },
586
- {
587
- key: "wf",
588
- hideOnMobile: true,
589
- header: "Workflow",
590
- cell: (r) =>
591
- r.workflow_name ? (
592
- <span className="text-xs">
593
- {r.workflow_name}
594
- {r.workflow_step_name && (
595
- <span className="text-muted-foreground">
596
- {" · "}
597
- {r.workflow_step_name}
598
- </span>
599
- )}
600
- </span>
601
- ) : null,
602
- },
603
- {
604
- key: "tok",
605
- hideOnMobile: true,
606
- header: "Tokens",
607
- align: "right",
608
- cell: (r) =>
609
- r.input_tokens === null && r.output_tokens === null ? (
610
- <span className="text-muted-foreground">–</span>
611
- ) : (
612
- <span className="font-mono text-xs">
613
- {count(r.input_tokens ?? 0)} / {count(r.output_tokens ?? 0)}
614
- </span>
615
- ),
616
- },
617
- {
618
- key: "d",
619
- header: "Duration",
620
- align: "right",
621
- cell: (r) => ms(r.duration),
622
- },
623
- {
624
- key: "cost",
625
- header: "Cost",
626
- align: "right",
627
- cell: (r) =>
628
- r.cost === null &&
629
- r.operation !== "tool" &&
630
- r.status !== "failed" ? (
631
- <span
632
- className="text-muted-foreground"
633
- title="No registry pricing"
634
- >
635
- unpriced
636
- </span>
637
- ) : (
638
- usd(r.cost)
639
- ),
640
- },
641
- {
642
- key: "in",
643
- hideOnMobile: true,
644
- header: "In",
645
- cell: (r) => {
646
- const to = href(r)
647
- return to ? (
648
- <Link className="font-mono text-xs hover:underline" href={to}>
649
- {r.execution_preview}
650
- </Link>
651
- ) : null
652
- },
653
- },
654
- {
655
- key: "content",
656
- header: "Detail",
657
- align: "right",
658
- cell: (r) => <Content call={r} />,
659
- },
660
- ]}
661
- />
662
- </EnvLayout>
663
- )
664
- }