rails_pulse 0.3.3 → 0.4.0.pre.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +272 -0
- data/README.md +44 -3
- data/app/assets/stylesheets/rails_pulse/application.css +21 -32
- data/app/assets/stylesheets/rails_pulse/components/backtrace.css +238 -0
- data/app/assets/stylesheets/rails_pulse/components/badge.css +32 -0
- data/app/assets/stylesheets/rails_pulse/components/button.css +2 -36
- data/app/assets/stylesheets/rails_pulse/components/card.css +8 -11
- data/app/assets/stylesheets/rails_pulse/components/chart.css +16 -3
- data/app/assets/stylesheets/rails_pulse/components/csp_safe_positioning.css +5 -39
- data/app/assets/stylesheets/rails_pulse/components/dashboard.css +0 -52
- data/app/assets/stylesheets/rails_pulse/components/deployments.css +39 -0
- data/app/assets/stylesheets/rails_pulse/components/dialog.css +0 -5
- data/app/assets/stylesheets/rails_pulse/components/input.css +0 -14
- data/app/assets/stylesheets/rails_pulse/components/layouts.css +0 -20
- data/app/assets/stylesheets/rails_pulse/components/menu.css +0 -16
- data/app/assets/stylesheets/rails_pulse/components/sidebar_menu.css +0 -16
- data/app/assets/stylesheets/rails_pulse/components/storage.css +187 -0
- data/app/assets/stylesheets/rails_pulse/components/tags.css +0 -14
- data/app/assets/stylesheets/rails_pulse/components/time_range_selector.css +5 -5
- data/app/assets/stylesheets/rails_pulse/components/utilities.css +0 -17
- data/app/controllers/concerns/chart_table_concern.rb +15 -3
- data/app/controllers/concerns/ransack_params_concern.rb +18 -0
- data/app/controllers/concerns/response_range_concern.rb +1 -1
- data/app/controllers/concerns/time_range_concern.rb +28 -14
- data/app/controllers/rails_pulse/application_controller.rb +104 -31
- data/app/controllers/rails_pulse/dashboard_controller.rb +9 -0
- data/app/controllers/rails_pulse/deployments_controller.rb +26 -5
- data/app/controllers/rails_pulse/exception_occurrences_controller.rb +19 -0
- data/app/controllers/rails_pulse/exceptions_controller.rb +119 -0
- data/app/controllers/rails_pulse/job_runs_controller.rb +1 -1
- data/app/controllers/rails_pulse/requests_controller.rb +1 -1
- data/app/controllers/rails_pulse/routes_controller.rb +7 -1
- data/app/controllers/rails_pulse/storage_controller.rb +7 -0
- data/app/controllers/rails_pulse/tags_controller.rb +2 -2
- data/app/helpers/rails_pulse/application_helper.rb +1 -0
- data/app/helpers/rails_pulse/backtrace_helper.rb +85 -0
- data/app/helpers/rails_pulse/breadcrumbs_helper.rb +4 -1
- data/app/helpers/rails_pulse/chart_helper.rb +0 -3
- data/app/helpers/rails_pulse/route_helper.rb +8 -16
- data/app/javascript/rails_pulse/application.js +29 -8
- data/app/javascript/rails_pulse/controllers/clipboard_controller.js +58 -0
- data/app/javascript/rails_pulse/controllers/index_controller.js +16 -10
- data/app/javascript/rails_pulse/controllers/pagination_controller.js +6 -2
- data/app/javascript/rails_pulse/controllers/popover_controller.js +4 -0
- data/app/models/concerns/rails_pulse/taggable.rb +11 -6
- data/app/models/rails_pulse/cards/base.rb +53 -0
- data/app/models/rails_pulse/dashboard/health_summary.rb +52 -0
- data/app/models/rails_pulse/dashboard/needs_attention.rb +73 -1
- data/app/models/rails_pulse/dashboard/storage_status.rb +490 -0
- data/app/models/rails_pulse/deployment.rb +47 -1
- data/app/models/rails_pulse/exception_group.rb +37 -0
- data/app/models/rails_pulse/exception_occurrence.rb +27 -0
- data/app/models/rails_pulse/exceptions/cards/exception_rate.rb +116 -0
- data/app/models/rails_pulse/exceptions/cards/open_groups.rb +49 -0
- data/app/models/rails_pulse/exceptions/cards/total_occurrences.rb +62 -0
- data/app/models/rails_pulse/exceptions/charts/occurrence_volume.rb +56 -0
- data/app/models/rails_pulse/exceptions/raw_data.rb +216 -0
- data/app/models/rails_pulse/jobs/cards/p95_duration.rb +8 -2
- data/app/models/rails_pulse/operation.rb +13 -4
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +8 -3
- data/app/models/rails_pulse/request.rb +5 -1
- data/app/models/rails_pulse/route.rb +71 -21
- data/app/models/rails_pulse/routes/cards/error_rates.rb +6 -3
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +8 -3
- data/app/models/rails_pulse/routes/tables/index.rb +8 -6
- data/app/models/rails_pulse/summary.rb +21 -10
- data/app/models/rails_pulse/tables/base.rb +4 -2
- data/app/services/rails_pulse/analysis/explain_plan_analyzer.rb +47 -12
- data/app/services/rails_pulse/exception_capture_service.rb +228 -0
- data/app/services/rails_pulse/exception_message_sanitizer.rb +152 -0
- data/app/services/rails_pulse/operations/change_point.rb +144 -0
- data/app/services/rails_pulse/operations/compare.rb +92 -0
- data/app/services/rails_pulse/operations/comparison.rb +126 -0
- data/app/services/rails_pulse/operations/metric.rb +63 -0
- data/app/services/rails_pulse/operations/series.rb +84 -0
- data/app/services/rails_pulse/operations/subject.rb +77 -0
- data/app/services/rails_pulse/route_controller_action_backfiller.rb +110 -0
- data/app/services/rails_pulse/route_merger.rb +99 -0
- data/app/services/rails_pulse/route_migrator.rb +139 -0
- data/app/services/rails_pulse/route_path_normalizer.rb +79 -0
- data/app/services/rails_pulse/route_recognizer.rb +47 -0
- data/app/services/rails_pulse/summary_service.rb +45 -0
- data/app/services/rails_pulse/tag_filter_service.rb +5 -3
- data/app/views/layouts/rails_pulse/_menu_items.html.erb +7 -0
- data/app/views/layouts/rails_pulse/_onboarding_banner.html.erb +8 -0
- data/app/views/layouts/rails_pulse/_setup_screen.html.erb +5 -5
- data/app/views/layouts/rails_pulse/application.html.erb +4 -4
- data/app/views/rails_pulse/components/_empty_state.html.erb +1 -1
- data/app/views/rails_pulse/components/_panel.html.erb +16 -6
- data/app/views/rails_pulse/components/_raw_data.html.erb +34 -0
- data/app/views/rails_pulse/dashboard/_chart_panel.html.erb +6 -8
- data/app/views/rails_pulse/dashboard/_deployments_panel.html.erb +64 -0
- data/app/views/rails_pulse/dashboard/_health_badge.html.erb +21 -10
- data/app/views/rails_pulse/dashboard/_storage_panel.html.erb +67 -0
- data/app/views/rails_pulse/dashboard/index.html.erb +22 -1
- data/app/views/rails_pulse/deployments/_status_badge.html.erb +5 -0
- data/app/views/rails_pulse/deployments/_table.html.erb +29 -0
- data/app/views/rails_pulse/deployments/index.html.erb +15 -0
- data/app/views/rails_pulse/deployments/show.html.erb +62 -0
- data/app/views/rails_pulse/exception_occurrences/show.html.erb +45 -0
- data/app/views/rails_pulse/exceptions/_backtrace.html.erb +33 -0
- data/app/views/rails_pulse/exceptions/_metric_cards.html.erb +6 -0
- data/app/views/rails_pulse/exceptions/_occurrences_table.html.erb +23 -0
- data/app/views/rails_pulse/exceptions/_status_badge.html.erb +6 -0
- data/app/views/rails_pulse/exceptions/_table.html.erb +33 -0
- data/app/views/rails_pulse/exceptions/index.html.erb +66 -0
- data/app/views/rails_pulse/exceptions/show.html.erb +52 -0
- data/app/views/rails_pulse/queries/_raw_data.html.erb +1 -23
- data/app/views/rails_pulse/requests/_table.html.erb +1 -1
- data/app/views/rails_pulse/routes/_requests_table.html.erb +5 -0
- data/app/views/rails_pulse/routes/_table.html.erb +22 -8
- data/app/views/rails_pulse/routes/index.html.erb +5 -1
- data/app/views/rails_pulse/routes/show.html.erb +5 -0
- data/app/views/rails_pulse/storage/show.html.erb +123 -0
- data/config/routes.rb +13 -4
- data/db/rails_pulse_migrate/20260506000001_create_rails_pulse_exceptions.rb +50 -0
- data/db/rails_pulse_migrate/20260507000001_add_actual_query_to_operations.rb +10 -5
- data/db/rails_pulse_migrate/20260610000001_add_controller_action_to_rails_pulse_routes.rb +8 -0
- data/db/rails_pulse_migrate/20260610000002_change_rails_pulse_routes_to_multi_verb_model.rb +185 -0
- data/db/rails_pulse_migrate/20260610000003_add_null_action_unique_index_to_routes.rb +32 -0
- data/db/rails_pulse_migrate/20260823000001_add_location_to_exception_groups.rb +12 -0
- data/db/rails_pulse_schema.rb +54 -6
- data/lib/generators/rails_pulse/base_methods.rb +2 -0
- data/lib/generators/rails_pulse/install_generator.rb +5 -0
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +54 -6
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +152 -16
- data/lib/generators/rails_pulse/upgrade_generator.rb +166 -38
- data/lib/rails_pulse/cleanup_service.rb +110 -15
- data/lib/rails_pulse/configuration.rb +125 -6
- data/lib/rails_pulse/engine.rb +32 -17
- data/lib/rails_pulse/installers/config_updater.rb +202 -0
- data/lib/rails_pulse/job_run_collector.rb +43 -16
- data/lib/rails_pulse/middleware/asset_server.rb +11 -0
- data/lib/rails_pulse/middleware/request_collector.rb +9 -4
- data/lib/rails_pulse/packaged_assets.rb +147 -0
- data/lib/rails_pulse/route_indexes.rb +66 -0
- data/lib/rails_pulse/subscribers/exception_subscriber.rb +36 -0
- data/lib/rails_pulse/tracker.rb +12 -9
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/rails_pulse.rb +1 -0
- data/lib/rails_pulse_server.ru +16 -47
- data/lib/tasks/rails_pulse.rake +83 -4
- data/lib/tasks/rails_pulse_assets.rake +15 -0
- data/lib/tasks/rails_pulse_tasks.rake +29 -0
- data/public/rails-pulse-assets/rails-pulse-icons.js +6 -2
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +18 -117
- metadata +78 -37
- data/Rakefile +0 -554
- data/app/javascript/rails_pulse/controllers/form_controller.js +0 -39
- data/app/javascript/rails_pulse/controllers/period_selector_controller.js +0 -19
- data/app/javascript/rails_pulse/controllers/timezone_controller.js +0 -89
- data/app/javascript/rails_pulse/theme.js +0 -416
- data/config/brakeman.ignore +0 -190
- data/config/brakeman.yml +0 -68
- data/config/initializers/rails_pulse.rb +0 -287
- data/public/rails-pulse-assets/csp-test.js +0 -120
- data/public/rails-pulse-assets/rails-pulse-icons.js.map +0 -13
- data/public/rails-pulse-assets/rails-pulse.css.map +0 -1
- data/public/rails-pulse-assets/rails-pulse.js.map +0 -7
- data/vendor/assets/javascripts/rails-pulse-icons.js +0 -93
- data/vendor/assets/javascripts/rails-pulse-icons.js.map +0 -13
- data/vendor/assets/javascripts/rails-pulse.js +0 -157
- data/vendor/assets/javascripts/rails-pulse.js.map +0 -7
- data/vendor/assets/stylesheets/rails-pulse.css +0 -1
- data/vendor/assets/stylesheets/rails-pulse.css.map +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 19b495bf5ea042672667ee099947d337731557f880af30f7d2dc26f507cbea0f
|
|
4
|
+
data.tar.gz: cb8946047ebb55a662742d42d143f47b5b3f3b982779fb289e4abfe418ac1c59
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 428db96f32b2624638e99aacad09ef5dbeac23b6e11df316f7db66757c997777e846907830993657f4c76c0ea81dbfc92246ad93e1f5f3cc1c5b53ca80cb6065
|
|
7
|
+
data.tar.gz: 3f73d50c25352fc8a3dbaabe8cee59a51624fbebb1d23dfc58208bf42add67a33a56003d98769610559cd8211f8dab76a7a478da6cac0883f7a0d0e6d07f6ebb
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Rails Pulse will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.4.0.pre.1] - 2026-09-03
|
|
11
|
+
|
|
12
|
+
This release contains a **breaking schema change** and requires a one-time data
|
|
13
|
+
migration. Read "Upgrading from 0.3.x" below before deploying. Take a database
|
|
14
|
+
backup first: the route migration is irreversible.
|
|
15
|
+
|
|
16
|
+
### Security
|
|
17
|
+
|
|
18
|
+
- **Job failure messages are redacted and bounded.** `rails_pulse_job_runs.error_message`
|
|
19
|
+
used to be stored raw and unbounded. It now goes through the same sanitizer as
|
|
20
|
+
exception messages: `key=value` / `key: value` / `"key":"value"` fragments are
|
|
21
|
+
masked whenever the key matches `config.filter_parameters` (same rules as logged
|
|
22
|
+
params), and MySQL `Duplicate entry '<value>'` values are redacted alongside
|
|
23
|
+
PostgreSQL `DETAIL:` clauses.
|
|
24
|
+
- **CSRF protection is now declared by the engine.** `RailsPulse::ApplicationController`
|
|
25
|
+
calls `protect_from_forgery with: :exception` itself instead of inheriting it from the
|
|
26
|
+
host's `default_protect_from_forgery`, which only `load_defaults 5.2+` enables. Hosts
|
|
27
|
+
on an older `load_defaults` had no CSRF protection on any engine endpoint (tags,
|
|
28
|
+
exception status, query reanalyze, session filters). Brakeman's `CheckForgerySetting`
|
|
29
|
+
is no longer skipped.
|
|
30
|
+
- **Authentication hooks fail closed.** An `authentication_method` that returned
|
|
31
|
+
`false` without rendering or redirecting used to grant access — `proc { current_user&.admin? }`
|
|
32
|
+
looked right and let every non-admin in. Literal `false` is now a 403; `nil`
|
|
33
|
+
(what the documented `unless … redirect_to … end` style returns on success) still allows.
|
|
34
|
+
- **EXPLAIN hardening.** The query analyzer now refuses SQL containing `;`, `--` or `/*`
|
|
35
|
+
anywhere (PostgreSQL's `execute` runs every statement in a string, so a captured
|
|
36
|
+
`SELECT …; UPDATE …` would have re-run the UPDATE on each page view), runs the
|
|
37
|
+
PostgreSQL EXPLAIN inside `SET LOCAL transaction_read_only = on` with a 5s
|
|
38
|
+
`statement_timeout` instead of `Timeout.timeout`, and always rolls its savepoint back.
|
|
39
|
+
- **Deployments API input is bounded.** `revision` is capped at 255 characters,
|
|
40
|
+
serialized `metadata` at 4 KB, and `started_at` may be at most one hour in the future
|
|
41
|
+
(a forged future marker would have stamped every later exception with a revision
|
|
42
|
+
that never shipped). `rails_pulse_deployments` now has a count cap
|
|
43
|
+
(`max_table_records`, default 1 000) enforced by `CleanupService`; deployments are
|
|
44
|
+
never pruned by age.
|
|
45
|
+
- **Backtrace source snippets are limited to `app/`, `lib/` and `config/routes.rb`**
|
|
46
|
+
(`.rb`, `.erb`, `.rake`, `.haml`, `.slim`, `.jbuilder`). Previously any file under
|
|
47
|
+
`Rails.root` was readable through a backtrace frame, including initializers with
|
|
48
|
+
inline keys, `config/database.yml`, and `.env`.
|
|
49
|
+
- **Standalone dashboard session cookie is `Secure` in production** (override with
|
|
50
|
+
`RAILS_PULSE_INSECURE_SESSION=1`). The rackup's "no Rails app" branch, which could
|
|
51
|
+
never boot (`NameError: uninitialized constant Rails`), is replaced with a clear
|
|
52
|
+
error; the deployment docs now give the working invocation.
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- **Exception tracking** — Captures unhandled exceptions from web requests and
|
|
57
|
+
background jobs, groups them by class and location, and displays backtraces (first
|
|
58
|
+
50 frames) with filtered request params in a new Exceptions tab. Exception
|
|
59
|
+
messages are redacted before storage: `key=value` / `key: value` / `"key":"value"`
|
|
60
|
+
fragments are masked whenever the key matches `config.filter_parameters`, and
|
|
61
|
+
MySQL `Duplicate entry '<value>'` values are redacted alongside PostgreSQL
|
|
62
|
+
`DETAIL:` clauses.
|
|
63
|
+
- **`track_exceptions` config option** — Gem default is `false`, so existing installs
|
|
64
|
+
do not begin capturing on upgrade; the upgrade generator inserts `false`
|
|
65
|
+
explicitly. The install template sets `true` for new apps.
|
|
66
|
+
- **`capture_exception_params` config option** — Captures request params alongside
|
|
67
|
+
each occurrence, filtered via Rails' `filter_parameters`. Occurrences whose params
|
|
68
|
+
exceed 10 KB after filtering are stored without params. Default `true`, and only
|
|
69
|
+
consulted while `track_exceptions` is on.
|
|
70
|
+
- **`exception_message_filter` config option** — an optional `->(message, exception)`
|
|
71
|
+
hook applied after the built-in redaction for app-specific patterns. If the hook
|
|
72
|
+
raises, the message is stored as `[FILTERED]` rather than unfiltered.
|
|
73
|
+
- **`authorize` config option** — a fail-closed predicate, `->(controller) { … }`
|
|
74
|
+
(or a zero-argument proc run in the controller). Anything falsy is a 403. Runs after
|
|
75
|
+
`authentication_method` when both are set, and replaces the HTTP Basic fallback on
|
|
76
|
+
its own. This is now the recommended way to gate the dashboard.
|
|
77
|
+
- **Upgrade generator syncs new initializer settings** — Appends keys (and
|
|
78
|
+
`max_table_records` entries) the host file does not already mention, without
|
|
79
|
+
rewriting existing values. Review with `git diff`.
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
|
|
83
|
+
- **BREAKING — route identity is now `[controller_action, path]`.** GET `/users`
|
|
84
|
+
(`users#index`) and POST `/users` (`users#create`) are distinct routes. Dynamic
|
|
85
|
+
paths are normalized at capture time (`/posts/42` → `/posts/:id`).
|
|
86
|
+
- **A one-time backfill is mandatory.** `rails rails_pulse:migrate_routes` backfills
|
|
87
|
+
`controller_action` (from the live router, then from request history), collapses
|
|
88
|
+
historical literal paths, and merges routes sharing an action (GET+POST `/sign_in`
|
|
89
|
+
→ `sessions#new`). A schema migrate alone leaves the Action column empty. The
|
|
90
|
+
upgrade generator and the dashboard both warn when the step is outstanding.
|
|
91
|
+
- **The JavaScript bundle is 66% smaller: 2.19 MB → 759 KB (754 KB → 248 KB gzipped).**
|
|
92
|
+
ECharts is now tree-shaken — `application.js` imports from `echarts/core` and
|
|
93
|
+
registers only the series and components the dashboard uses, instead of the
|
|
94
|
+
`echarts` barrel that bundled every chart type including maps, sankey and gauge.
|
|
95
|
+
Charts outside that set must now register their module explicitly; see
|
|
96
|
+
`docs/stimulus_echarts_usage.md`. The stylesheet is 5% smaller.
|
|
97
|
+
|
|
98
|
+
### Removed
|
|
99
|
+
|
|
100
|
+
- **BREAKING — `rails_pulse_routes.method` is dropped.** The HTTP verb now lives on
|
|
101
|
+
each request (`rails_pulse_requests.method`); a route carries an `http_methods`
|
|
102
|
+
array instead. All application, worker, and dashboard processes must be restarted
|
|
103
|
+
together against the migrated schema. A rolling restart that leaves 0.3.x
|
|
104
|
+
processes running will silently drop request tracking and 500 the routes dashboard.
|
|
105
|
+
- **BREAKING — the route migration is irreversible.** `db:rollback` raises
|
|
106
|
+
`ActiveRecord::IrreversibleMigration` on
|
|
107
|
+
`ChangeRailsPulseRoutesToMultiVerbModel`, and routes merged by
|
|
108
|
+
`rails_pulse:migrate_routes` are deleted with no audit trail. Recovery from a bad
|
|
109
|
+
upgrade is restore-from-backup only.
|
|
110
|
+
- **Three unreachable Stimulus controllers** — `form`, `timezone` and
|
|
111
|
+
`period_selector` — were bundled and registered despite no view referencing them.
|
|
112
|
+
`timezone` also ran a `MutationObserver` over the whole document body.
|
|
113
|
+
- **`theme.js`**, which registered a chart theme that `application.js` immediately
|
|
114
|
+
overwrote. Its UMD wrapper called `require('echarts')`, which alone would have
|
|
115
|
+
defeated the tree-shaking above.
|
|
116
|
+
- **Dead CSS**: unused css-zero ports, the removed dashboard period selector's styles,
|
|
117
|
+
and the disabled `toolbox` chart option.
|
|
118
|
+
- **`csp-test.js` is no longer shipped in the gem.** Its page is mounted only when
|
|
119
|
+
`Rails.env.local?`, so it was unreachable in a published gem. It remains in the
|
|
120
|
+
repository for development and the CSP compliance system test.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- **Tag filters work on SQLite for tags containing `_`.** The `LIKE` patterns were
|
|
125
|
+
escaped but issued without an `ESCAPE` clause; SQLite has no default escape
|
|
126
|
+
character, so `my_tag` never matched and "hide records tagged my_tag" hid nothing.
|
|
127
|
+
All three adapters now get an explicit `ESCAPE '!'`.
|
|
128
|
+
- **Hand-edited query strings no longer 500.** `?q=string`, unparseable custom date
|
|
129
|
+
ranges, unparseable `occurred_at` filters, a non-array `enabled_tags`, and
|
|
130
|
+
oversized or unparseable session time filters all fall back to defaults instead of
|
|
131
|
+
raising. Session-stored presets and performance thresholds are validated against
|
|
132
|
+
the known values.
|
|
133
|
+
- **Chart click/zoom got slower the more you switched chart tabs.** The requests,
|
|
134
|
+
routes and queries index pages attached a fresh `document` mouseup listener every
|
|
135
|
+
time a chart tab was re-selected, and only the most recent one was ever removed.
|
|
136
|
+
After N switches a single click anywhere on the page re-ran the zoom handler N
|
|
137
|
+
times. The listener is now bound once per controller.
|
|
138
|
+
- **Zooming onto the first column of a category chart reset the range.** Visible-range
|
|
139
|
+
extraction used `dataZoom.startValue || 0`, so a legitimate index of `0` fell through
|
|
140
|
+
to the full-axis fallback.
|
|
141
|
+
- **Hover popovers could throw after a table refresh.** `popover_controller` left its
|
|
142
|
+
show/hide timers running on disconnect, so a pending 700ms open could fire against
|
|
143
|
+
an element a fetch had already replaced.
|
|
144
|
+
- **The time range selector's hover border was invisible.** It referenced
|
|
145
|
+
`--color-border-emphasis`, which is defined nowhere, so the declaration was invalid
|
|
146
|
+
at computed-value time and the border fell back to `currentColor`. Its light-mode
|
|
147
|
+
rules also never applied on a first visit, because `data-color-scheme` is only set
|
|
148
|
+
after an explicit toggle.
|
|
149
|
+
- **`[popover].positioned` was declared twice** at equal specificity, so correct
|
|
150
|
+
popover placement depended on CSS file load order. Merged into one rule.
|
|
151
|
+
- Fixed upgrading when using a separate database installation.
|
|
152
|
+
- Separate-database installs set `schema_dump: false`, so `db:migrate` no longer
|
|
153
|
+
dumps or loads `db/rails_pulse_structure.sql` (#189).
|
|
154
|
+
- Fixed `assets:precompile` failing on memory-constrained hosts by not registering
|
|
155
|
+
dashboard assets with the host Sprockets pipeline. Precompile copies the pre-built
|
|
156
|
+
files into `public/assets` (digested, no compressor) so `config.asset_host` and
|
|
157
|
+
CDN-only CSP work; development and hosts without a pipeline still use
|
|
158
|
+
`/rails-pulse-assets/<gem-version>/...`.
|
|
159
|
+
- SQLite `schema.rb` now keeps the partial unique index on unrecognised routes
|
|
160
|
+
(`WHERE controller_action IS NULL`). A raw `CREATE INDEX` was dumped without the
|
|
161
|
+
predicate, so `db:schema:load` and parallel tests unique-constrained every path.
|
|
162
|
+
|
|
163
|
+
### Upgrading from 0.3.x
|
|
164
|
+
|
|
165
|
+
Applies to every 0.3.x release (0.3.0 through 0.3.3). **Back up your database first.**
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
bundle update rails_pulse
|
|
169
|
+
rails generate rails_pulse:upgrade
|
|
170
|
+
rails db:migrate # separate Pulse database: rails db:migrate:rails_pulse
|
|
171
|
+
rails rails_pulse:migrate_routes # required — schema migrate alone leaves Action empty
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Then restart **all** processes together, not as a rolling deploy.
|
|
175
|
+
|
|
176
|
+
Separate-database hosts: add `schema_dump: false` to the `rails_pulse` entry in
|
|
177
|
+
`config/database.yml` and delete `db/rails_pulse_structure.sql` if it exists. Do not
|
|
178
|
+
run `db:setup` / `db:prepare` as a substitute for `db:migrate:rails_pulse`.
|
|
179
|
+
|
|
180
|
+
Exception tracking stays off after upgrading. Set `config.track_exceptions = true`
|
|
181
|
+
once you have reviewed what is captured.
|
|
182
|
+
|
|
183
|
+
## [0.3.3] - 2026-06-23
|
|
184
|
+
|
|
185
|
+
- **Deployment tracking** — Record deployments via `POST /rails_pulse/deployments` or `rake rails_pulse:record_deployment[sha]`. Deployments appear as vertical marker lines on performance charts so you can correlate releases with regressions
|
|
186
|
+
- **`deployment_api_token` config option** — Secures the deployments endpoint with a token header for CI/CD use
|
|
187
|
+
- All multi-series charts now use a native ECharts time axis (`[timestamp_ms, value]` pairs) instead of a separate labels array, enabling deployment markers and better zoom behaviour
|
|
188
|
+
|
|
189
|
+
## [0.3.0] - 2026-04-30
|
|
190
|
+
|
|
191
|
+
This is the largest release to date — a full UI overhaul across every section of
|
|
192
|
+
the dashboard. Charts are now switchable, the dashboard surfaces health status
|
|
193
|
+
automatically, and the underlying controller and model code has been substantially
|
|
194
|
+
refactored. Existing installs need to run the two new migrations. Hopefully future
|
|
195
|
+
releases will be smaller and more incremental than this one.
|
|
196
|
+
|
|
197
|
+
### Added
|
|
198
|
+
|
|
199
|
+
- **New chart types across all sections** — Routes, queries, and jobs now have dedicated chart panels with switchable chart types (response time percentiles, request volume, error rate, execution volume, duration, failure rate)
|
|
200
|
+
- **Dashboard health summary** — New `HealthSummary` model surfaces an overall health status and highlights routes/queries/jobs that need attention
|
|
201
|
+
- **Dashboard "Needs Attention" section** — Automatically surfaces slow routes, high error rates, and problematic jobs without manual digging
|
|
202
|
+
- **Storage pressure indicator** — Tracks and displays database storage growth so you know when to adjust retention settings
|
|
203
|
+
- **Flame graph view for requests** — Request detail pages now include a flame graph visualisation of operation timing
|
|
204
|
+
- **P95 duration tracking for jobs** — Job runs now record and display p95 duration alongside average duration
|
|
205
|
+
- **Database load metric for queries** — New card and chart tracking cumulative database load (execution count × avg duration) over time
|
|
206
|
+
- **Diagnostic fields for queries** — Query show pages now surface diagnostic information alongside existing analysis
|
|
207
|
+
- **Chart series toggle** — Show/hide individual series on charts without leaving the page
|
|
208
|
+
- **Performance status concern** — Shared `HasPerformanceStatus` concern for models that report a health status
|
|
209
|
+
- **Metric strip component** — New compact summary strip component for displaying multiple metrics inline
|
|
210
|
+
- **Setup banner** — Onboarding banner shown to users who haven't completed setup
|
|
211
|
+
- **Time range selector** — Redesigned time range UI with a custom date range option
|
|
212
|
+
- **Suggestions service** — Query show pages surface optimisation suggestions (caching, controller, SQL, HTTP, view) via dedicated suggestion services
|
|
213
|
+
- **Statistics module** — New `RailsPulse::Statistics` module for shared statistical calculations
|
|
214
|
+
- **Cleanup stats reporter** — Detailed reporting on what the cleanup job removed each run
|
|
215
|
+
- **Cleanup task runner** — Extracted cleanup orchestration into `CleanupTaskRunner` for testability
|
|
216
|
+
- **Config and migration installers** — Extracted install/upgrade logic into `ConfigInstaller` and `MigrationInstaller` classes
|
|
217
|
+
- **Schema parser** — New `SchemaParser` for reading and diffing schema state during upgrades
|
|
218
|
+
- **Icon helper** — Centralised `IconHelper` for rendering SVG icons
|
|
219
|
+
- **Route helper** — Centralised `RouteHelper` for building internal dashboard links
|
|
220
|
+
- **CSP helper** — Dedicated `CspHelper` for content security policy nonce management
|
|
221
|
+
- **Backfill summaries job** — New job to backfill summary records for historical data
|
|
222
|
+
|
|
223
|
+
### Changed
|
|
224
|
+
|
|
225
|
+
- **Dashboard redesigned** — Full dashboard overhaul with health summary, attention sections, and chart panels replacing the previous card layout
|
|
226
|
+
- **Routes, queries, and jobs index pages redesigned** — Consistent layout with metric cards, switchable chart tabs, and paginated tables
|
|
227
|
+
- **Metric card component updated** — Cards now support a richer data structure with trend indicators
|
|
228
|
+
- **Application controller refactored** — Controller concerns split into focused modules (`ChartTableConcern`, `MetricCardConcern`, `PaginationConcern`, `SessionFiltersConcern`, `TagFilterConcern`, `TimeRangeConcern`, `ZoomRangeConcern`)
|
|
229
|
+
- **`normalized_sql` column expanded to `text`** — Removes the 1000-character limit on PostgreSQL and MySQL (migration provided for existing installs)
|
|
230
|
+
- **Diagnostic fields added to queries table** — New columns captured at instrumentation time
|
|
231
|
+
- **Summary job and service refactored** — Cleaner separation between job scheduling and summary calculation logic
|
|
232
|
+
- **Cleanup job refactored** — Now uses `CleanupTaskRunner` and reports statistics via `CleanupStatsReporter`
|
|
233
|
+
- **Configuration expanded** — New configuration options exposed in the initializer template
|
|
234
|
+
- **Upgrade generator simplified** — Generator now delegates to `MigrationInstaller` and `ConfigInstaller`
|
|
235
|
+
- **Asset server middleware updated** — Improved asset serving reliability
|
|
236
|
+
- **Request collector middleware updated** — Performance improvements and cleaner instrumentation
|
|
237
|
+
- **Operation subscriber refactored** — Cleaner event handling and reduced complexity
|
|
238
|
+
- **Seeds refactored** — Dummy app seeds split into focused files under `db/seeds/rails_pulse/`
|
|
239
|
+
- **Test coverage significantly expanded** — Controller concerns, helpers, models, services, and system tests all substantially extended
|
|
240
|
+
|
|
241
|
+
### Removed
|
|
242
|
+
|
|
243
|
+
- `StatusHelper` removed — status rendering consolidated into model concerns and view components
|
|
244
|
+
- `ChartFormatters` helper removed — chart formatting moved into chart model classes
|
|
245
|
+
- `chart_formatters_test.rb` removed alongside the helper
|
|
246
|
+
- Slow queries and slow routes table models removed — replaced by the Needs Attention system
|
|
247
|
+
- Unused `average_response_time` and `p95_response_time` dashboard chart classes removed
|
|
248
|
+
- `requests/tables/index.rb` and related test removed — requests index now uses a shared approach
|
|
249
|
+
- `routes/cards/average_response_times` and `routes/charts/average_response_times` removed — replaced by percentile-based equivalents
|
|
250
|
+
- `jobs/cards/average_duration` and `jobs/cards/total_jobs` removed — replaced by updated cards
|
|
251
|
+
- Timezone controller removed
|
|
252
|
+
- Application mailer stub removed
|
|
253
|
+
|
|
254
|
+
## [0.2.7] - 2026-04-17
|
|
255
|
+
|
|
256
|
+
No changelog entry — see git history.
|
|
257
|
+
|
|
258
|
+
## [0.2.6] - 2026-04-15
|
|
259
|
+
|
|
260
|
+
No changelog entry — see git history.
|
|
261
|
+
|
|
262
|
+
## [0.2.5] - 2026-04-14
|
|
263
|
+
|
|
264
|
+
No changelog entry — see git history.
|
|
265
|
+
|
|
266
|
+
[Unreleased]: https://github.com/railspulse/rails_pulse/compare/v0.4.0.pre.1...HEAD
|
|
267
|
+
[0.4.0.pre.1]: https://github.com/railspulse/rails_pulse/compare/v0.3.3...v0.4.0.pre.1
|
|
268
|
+
[0.3.3]: https://github.com/railspulse/rails_pulse/compare/v0.3.2...v0.3.3
|
|
269
|
+
[0.3.0]: https://github.com/railspulse/rails_pulse/compare/v0.2.7...v0.3.0
|
|
270
|
+
[0.2.7]: https://github.com/railspulse/rails_pulse/compare/v0.2.6...v0.2.7
|
|
271
|
+
[0.2.6]: https://github.com/railspulse/rails_pulse/compare/v0.2.5...v0.2.6
|
|
272
|
+
[0.2.5]: https://github.com/railspulse/rails_pulse/releases/tag/v0.2.5
|
data/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|

|
|
14
14
|

|
|
15
15
|
|
|
16
|
-
Rails Pulse is a Rails engine that monitors your app's performance from the inside. It tracks slow requests, N+1 queries, SQL performance, and
|
|
16
|
+
Rails Pulse is a Rails engine that monitors your app's performance from the inside. It tracks slow requests, N+1 queries, SQL performance, background jobs, and unhandled exceptions. All data stays in your own database, no third-party cloud, no SaaS subscription, no data leaving your servers.
|
|
17
17
|
|
|
18
18
|
<table border="0">
|
|
19
19
|
<tr border="0" style="border:0">
|
|
@@ -60,6 +60,32 @@ RailsPulse::CleanupJob.perform_later # cron: 0 1 * * *
|
|
|
60
60
|
|
|
61
61
|
Your dashboard is now at `http://localhost:3000/rails_pulse`.
|
|
62
62
|
|
|
63
|
+
## Upgrading
|
|
64
|
+
|
|
65
|
+
From 0.3.3:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
bundle update rails_pulse
|
|
69
|
+
rails generate rails_pulse:upgrade
|
|
70
|
+
rails db:migrate # or: rails db:migrate:rails_pulse
|
|
71
|
+
rails rails_pulse:migrate_routes # required — fills Action and merges same-action paths
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Restart all processes after migrate. This release changes how routes are stored (`method` moves off the route onto each request), so mixed old/new processes are not supported.
|
|
75
|
+
|
|
76
|
+
The upgrade generator appends new settings to `config/initializers/rails_pulse.rb` without rewriting what you already set. Review with `git diff` and keep or discard hunks.
|
|
77
|
+
|
|
78
|
+
Exception tracking is **off** for existing installs. The generator inserts `config.track_exceptions = false`; set it to `true` after migrating to opt in:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
config.track_exceptions = true
|
|
82
|
+
config.capture_exception_params = true # params are filtered via Rails' filter_parameters
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Separate-database hosts: set `schema_dump: false` on the `rails_pulse` entry in `config/database.yml` and delete `db/rails_pulse_structure.sql` if that file exists.
|
|
86
|
+
|
|
87
|
+
If you previously added `rails-pulse.js` / `rails-pulse.css` to `config.assets.precompile`, remove those entries — the gem no longer registers dashboard assets with Sprockets (that re-minify OOMs small hosts). Production deploys that use `config.asset_host` or a CDN-only CSP should run `assets:precompile` so `rails_pulse:install_assets` copies digested files into `public/assets`.
|
|
88
|
+
|
|
63
89
|
Full install guide: [railspulse.com/documentation/installation](https://railspulse.com/documentation/installation)
|
|
64
90
|
|
|
65
91
|
Separate database setup: [railspulse.com/documentation/database](https://railspulse.com/documentation/database)
|
|
@@ -81,6 +107,9 @@ RailsPulse.configure do |config|
|
|
|
81
107
|
config.track_jobs = true
|
|
82
108
|
config.capture_job_arguments = false # keep false to protect sensitive data
|
|
83
109
|
|
|
110
|
+
config.track_exceptions = true
|
|
111
|
+
config.capture_exception_params = true # params are filtered via Rails' filter_parameters
|
|
112
|
+
|
|
84
113
|
config.full_retention_period = 30.days
|
|
85
114
|
end
|
|
86
115
|
```
|
|
@@ -89,11 +118,20 @@ Full configuration reference: [railspulse.com/documentation/advanced](https://ra
|
|
|
89
118
|
|
|
90
119
|
## Authentication
|
|
91
120
|
|
|
92
|
-
Rails Pulse has no built-in
|
|
121
|
+
Rails Pulse has no built-in user accounts; you protect the dashboard using your app's existing auth. Authentication is on by default outside development and test, and with nothing configured it falls back to HTTP Basic against `RAILS_PULSE_USERNAME` / `RAILS_PULSE_PASSWORD` (denying everything if the password is unset).
|
|
122
|
+
|
|
123
|
+
The simplest hook is a predicate that receives the controller and returns `true` to allow — anything else is a 403:
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
RailsPulse.configure do |config|
|
|
127
|
+
config.authorize = ->(controller) { controller.current_user&.admin? }
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
If you need to redirect to a login page instead, use `authentication_method`, which runs inside the controller and denies by rendering or redirecting:
|
|
93
132
|
|
|
94
133
|
```ruby
|
|
95
134
|
RailsPulse.configure do |config|
|
|
96
|
-
config.authentication_enabled = true
|
|
97
135
|
config.authentication_redirect_path = "/login"
|
|
98
136
|
|
|
99
137
|
config.authentication_method = proc {
|
|
@@ -104,6 +142,8 @@ RailsPulse.configure do |config|
|
|
|
104
142
|
end
|
|
105
143
|
```
|
|
106
144
|
|
|
145
|
+
Returning `false` from `authentication_method` without responding is also treated as a denial, but `nil` (what `unless … end` returns on success) allows the request — so keep predicate-style checks in `authorize`.
|
|
146
|
+
|
|
107
147
|
Authentication guide: [railspulse.com/documentation/authentication](https://railspulse.com/documentation/authentication)
|
|
108
148
|
|
|
109
149
|
## Features
|
|
@@ -111,6 +151,7 @@ Authentication guide: [railspulse.com/documentation/authentication](https://rail
|
|
|
111
151
|
- **Request monitoring** — every request is timed and stored with its route, status, SQL count, and duration. Slow requests are flagged automatically based on thresholds you control.
|
|
112
152
|
- **Query analysis** — captures the queries behind each request, detects N+1 patterns, and tracks normalized SQL across requests so you can see which queries are hurting you in production, not just in development.
|
|
113
153
|
- **Job tracking** — monitors background job duration, queue wait time, and failure rates. Works with any Active Job adapter.
|
|
154
|
+
- **Exception tracking** — captures unhandled exceptions from web requests and background jobs, groups them by class and location, and shows full backtraces with filtered request params. See recurring errors in production without a separate error monitoring service.
|
|
114
155
|
- **System health bar** — at-a-glance dashboard summary showing healthy, slow, and critical counts across your routes, queries, jobs, and storage. Lets you see the overall state of your app before drilling into specifics.
|
|
115
156
|
- **No data leaves your app** — everything is stored in your own database. No third-party cloud, no SaaS subscription, no outbound connections.
|
|
116
157
|
- **Low overhead** — tracking is async and uses a thread-local flag to skip recording Rails Pulse's own internal requests.
|
|
@@ -41,6 +41,27 @@ html[data-color-scheme="dark"] .badge--negative-inverse {
|
|
|
41
41
|
--badge-background: rgb(47, 47, 47);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
html[data-color-scheme="dark"] .badge--get {
|
|
45
|
+
--badge-background: var(--blue-950);
|
|
46
|
+
--badge-color: var(--blue-200);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
html[data-color-scheme="dark"] .badge--post {
|
|
50
|
+
--badge-background: var(--green-950);
|
|
51
|
+
--badge-color: var(--green-200);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
html[data-color-scheme="dark"] .badge--put,
|
|
55
|
+
html[data-color-scheme="dark"] .badge--patch {
|
|
56
|
+
--badge-background: var(--amber-950);
|
|
57
|
+
--badge-color: var(--amber-200);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
html[data-color-scheme="dark"] .badge--delete {
|
|
61
|
+
--badge-background: var(--red-950);
|
|
62
|
+
--badge-color: var(--red-200);
|
|
63
|
+
}
|
|
64
|
+
|
|
44
65
|
html[data-color-scheme="dark"] .input {
|
|
45
66
|
--input-background: #535252;
|
|
46
67
|
--input-border-color: #7e7d7d;
|
|
@@ -161,19 +182,6 @@ html[data-color-scheme="dark"] .input {
|
|
|
161
182
|
z-index: 10;
|
|
162
183
|
}
|
|
163
184
|
|
|
164
|
-
.flame-bar-label {
|
|
165
|
-
font-size: 11px;
|
|
166
|
-
white-space: nowrap;
|
|
167
|
-
overflow: hidden;
|
|
168
|
-
text-overflow: ellipsis;
|
|
169
|
-
padding: 0 5px;
|
|
170
|
-
line-height: 20px;
|
|
171
|
-
color: rgba(255, 255, 255, 0.92);
|
|
172
|
-
display: block;
|
|
173
|
-
pointer-events: none;
|
|
174
|
-
user-select: none;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
185
|
.flame-time-axis {
|
|
178
186
|
position: relative;
|
|
179
187
|
height: 20px;
|
|
@@ -232,22 +240,3 @@ html[data-color-scheme="dark"] .input {
|
|
|
232
240
|
font-size: 11px;
|
|
233
241
|
}
|
|
234
242
|
|
|
235
|
-
/* REQUEST OPERATIONS BAR */
|
|
236
|
-
.bar-container {
|
|
237
|
-
height:10px;
|
|
238
|
-
position:relative
|
|
239
|
-
}
|
|
240
|
-
.bar {
|
|
241
|
-
background-color:#727579;
|
|
242
|
-
height:100%;
|
|
243
|
-
position:absolute;
|
|
244
|
-
top:0
|
|
245
|
-
}
|
|
246
|
-
.bar:first-child {
|
|
247
|
-
border-bottom-left-radius:1px;
|
|
248
|
-
border-top-left-radius:1px
|
|
249
|
-
}
|
|
250
|
-
.bar:last-child {
|
|
251
|
-
border-bottom-right-radius:1px;
|
|
252
|
-
border-top-right-radius:1px
|
|
253
|
-
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
.backtrace {
|
|
2
|
+
list-style: none;
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
6
|
+
font-size: 12px;
|
|
7
|
+
border-radius: 6px;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.backtrace__frame {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
border-bottom: 1px solid var(--color-border-light);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.backtrace__frame:last-child {
|
|
18
|
+
border-bottom: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.backtrace__row {
|
|
22
|
+
display: flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: space-between;
|
|
25
|
+
gap: 8px;
|
|
26
|
+
padding: 7px 12px 7px 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* App frames: highlighted with a coloured left accent */
|
|
30
|
+
.backtrace__frame--app {
|
|
31
|
+
background-color: rgba(59, 130, 246, 0.08);
|
|
32
|
+
border-left: 3px solid var(--blue-400, #3b82f6);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Gem/stdlib frames: muted */
|
|
36
|
+
.backtrace__frame--gem {
|
|
37
|
+
background-color: var(--color-surface);
|
|
38
|
+
border-left: 3px solid transparent;
|
|
39
|
+
opacity: 0.75;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.backtrace__frame--gem:hover {
|
|
43
|
+
opacity: 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.backtrace__location {
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: baseline;
|
|
49
|
+
gap: 0;
|
|
50
|
+
flex: 1;
|
|
51
|
+
min-width: 0;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.backtrace__path {
|
|
56
|
+
color: var(--color-text-subtle);
|
|
57
|
+
white-space: nowrap;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
text-overflow: ellipsis;
|
|
60
|
+
flex-shrink: 1;
|
|
61
|
+
min-width: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.backtrace__file {
|
|
65
|
+
color: var(--color-text);
|
|
66
|
+
font-weight: 700;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
flex-shrink: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.backtrace__method {
|
|
72
|
+
flex-shrink: 0;
|
|
73
|
+
color: var(--color-text-subtle);
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
font-size: 11px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.backtrace__method span {
|
|
79
|
+
color: var(--color-text);
|
|
80
|
+
font-weight: 500;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Source expand toggle — only the chevron is interactive */
|
|
84
|
+
|
|
85
|
+
.backtrace__source-details {
|
|
86
|
+
flex-shrink: 0;
|
|
87
|
+
line-height: 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.backtrace__toggle-spacer {
|
|
91
|
+
flex-shrink: 0;
|
|
92
|
+
width: 40px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.backtrace__toggle {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
width: 40px;
|
|
100
|
+
height: 40px;
|
|
101
|
+
cursor: pointer;
|
|
102
|
+
list-style: none;
|
|
103
|
+
color: var(--color-text-subtle);
|
|
104
|
+
font-size: 32px;
|
|
105
|
+
border-radius: 3px;
|
|
106
|
+
transition: background-color 0.1s ease, color 0.1s ease;
|
|
107
|
+
user-select: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.backtrace__toggle::-webkit-details-marker {
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.backtrace__toggle::after {
|
|
115
|
+
content: "›";
|
|
116
|
+
display: block;
|
|
117
|
+
transition: transform 0.15s ease;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.backtrace__toggle:hover {
|
|
121
|
+
background-color: var(--color-bg-hover, rgba(0,0,0,0.06));
|
|
122
|
+
color: var(--color-text);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.backtrace__source-details[open] .backtrace__toggle::after {
|
|
126
|
+
transform: rotate(90deg);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Source code panel */
|
|
130
|
+
|
|
131
|
+
.backtrace__source {
|
|
132
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
133
|
+
font-size: 12px;
|
|
134
|
+
background-color: var(--color-bg-subtle, #f9fafb);
|
|
135
|
+
border-top: 1px solid var(--color-border-light);
|
|
136
|
+
overflow-x: auto;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.backtrace__source--hidden {
|
|
140
|
+
display: none;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.backtrace__frame:has(.backtrace__source-details[open]) .backtrace__source {
|
|
144
|
+
display: block;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.backtrace__source-line {
|
|
148
|
+
display: flex;
|
|
149
|
+
gap: 0;
|
|
150
|
+
line-height: 1.6;
|
|
151
|
+
white-space: pre;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.backtrace__source-line--current {
|
|
155
|
+
background-color: rgba(234, 179, 8, 0.15);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.backtrace__source-lineno {
|
|
159
|
+
flex-shrink: 0;
|
|
160
|
+
width: 44px;
|
|
161
|
+
padding: 0 12px 0 8px;
|
|
162
|
+
text-align: right;
|
|
163
|
+
color: var(--color-text-subtle);
|
|
164
|
+
border-right: 1px solid var(--color-border-light);
|
|
165
|
+
user-select: none;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.backtrace__source-line--current .backtrace__source-lineno {
|
|
169
|
+
color: rgba(161, 98, 7, 0.9);
|
|
170
|
+
background-color: rgba(234, 179, 8, 0.2);
|
|
171
|
+
font-weight: 700;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.backtrace__source-code {
|
|
175
|
+
padding: 0 12px;
|
|
176
|
+
color: var(--color-text);
|
|
177
|
+
flex: 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Request params */
|
|
181
|
+
|
|
182
|
+
.exception-params {
|
|
183
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
184
|
+
font-size: 12px;
|
|
185
|
+
border-radius: 6px;
|
|
186
|
+
overflow: hidden;
|
|
187
|
+
border: 1px solid var(--color-border-light);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.exception-params__row {
|
|
191
|
+
display: flex;
|
|
192
|
+
gap: 16px;
|
|
193
|
+
padding: 6px 12px;
|
|
194
|
+
border-bottom: 1px solid var(--color-border-light);
|
|
195
|
+
align-items: baseline;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.exception-params__row:last-child {
|
|
199
|
+
border-bottom: none;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
.exception-params__key {
|
|
204
|
+
flex-shrink: 0;
|
|
205
|
+
width: 200px;
|
|
206
|
+
color: var(--color-text-subtle);
|
|
207
|
+
overflow: hidden;
|
|
208
|
+
text-overflow: ellipsis;
|
|
209
|
+
white-space: nowrap;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.exception-params__value {
|
|
213
|
+
color: var(--color-text);
|
|
214
|
+
word-break: break-all;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* Dark mode */
|
|
218
|
+
html[data-color-scheme="dark"] .backtrace__frame--app {
|
|
219
|
+
background-color: rgba(59, 130, 246, 0.15);
|
|
220
|
+
border-left-color: var(--blue-400, #3b82f6);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
html[data-color-scheme="dark"] .backtrace__frame--gem {
|
|
224
|
+
background-color: transparent;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
html[data-color-scheme="dark"] .backtrace__source {
|
|
228
|
+
background-color: rgba(0, 0, 0, 0.3);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
html[data-color-scheme="dark"] .backtrace__source-line--current {
|
|
232
|
+
background-color: rgba(234, 179, 8, 0.1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
html[data-color-scheme="dark"] .backtrace__source-line--current .backtrace__source-lineno {
|
|
236
|
+
color: rgba(253, 224, 71, 0.8);
|
|
237
|
+
background-color: rgba(234, 179, 8, 0.12);
|
|
238
|
+
}
|