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