rails_pulse 0.4.0.pre.2 → 0.4.0.pre.4
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 +77 -171
- data/README.md +2 -0
- data/app/controllers/rails_pulse/application_controller.rb +60 -1
- data/app/controllers/rails_pulse/deployments_controller.rb +1 -4
- data/app/helpers/rails_pulse/breadcrumbs_helper.rb +6 -3
- data/app/models/rails_pulse/deployment.rb +6 -0
- data/app/services/rails_pulse/exception_capture_service.rb +2 -0
- data/app/services/rails_pulse/route_controller_action_backfiller.rb +0 -4
- data/app/views/rails_pulse/shared/schema_outdated.html.erb +96 -0
- data/exe/rails_pulse_server +9 -0
- data/lib/generators/rails_pulse/install_generator.rb +2 -0
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +22 -1
- data/lib/generators/rails_pulse/upgrade_generator.rb +56 -5
- data/lib/rails_pulse/configuration.rb +21 -0
- data/lib/rails_pulse/engine.rb +5 -2
- data/lib/rails_pulse/installers/config_updater.rb +17 -0
- data/lib/rails_pulse/job_run_collector.rb +1 -1
- data/lib/rails_pulse/middleware/asset_server.rb +5 -4
- data/lib/rails_pulse/schema_check.rb +136 -0
- data/lib/rails_pulse/standalone.rb +50 -0
- data/lib/rails_pulse/tasks/status_reporter.rb +217 -0
- data/lib/rails_pulse/tracker.rb +1 -0
- data/lib/rails_pulse/version.rb +1 -1
- data/lib/rails_pulse.rb +2 -0
- data/lib/rails_pulse_server.ru +37 -4
- data/lib/tasks/rails_pulse.rake +48 -2
- data/lib/tasks/rails_pulse_tasks.rake +5 -0
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50a1c2016eee54e3ceda7d6e390669b7dc3f427a637cc975a50e9d682262411a
|
|
4
|
+
data.tar.gz: dfd20c8b99bdb1f3986aa143fb8cfe5aaf3935f7ba7cd43e4c6883721de30ad7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: caaf4f2932c7c639e7684fe3a8f4a789b4fe2d7a6b888d9cbeaf302487319efe758b91699f0eef1372cdad385f12269a1a78d9989bf29f855c20bf86c1c322e2
|
|
7
|
+
data.tar.gz: 852e0524002c525ca784c981c21234a750a2e5d9bc29024290c49d653df4fc806edd0c23e767b9e26be945ed81fafa37cf7474a7354e89ff533cf92a142eafda
|
data/CHANGELOG.md
CHANGED
|
@@ -7,185 +7,94 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0.pre.4] - 2026-09-07
|
|
11
|
+
|
|
12
|
+
## [0.4.0.pre.3] - 2026-09-06
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Shell-based deployment tracking.** `rake rails_pulse:record_deployment` and the new `rake rails_pulse:finish_deployment[revision]` let release scripts record and close deployments without the HTTP API or a token.
|
|
17
|
+
- **Standalone mode improvements.** Dashboard links now resolve correctly when served at `/`, and a new `config.standalone_authentication_method` (with an HTTP Basic fallback) replaces host-app authentication, which the standalone process can't use.
|
|
18
|
+
- **`rails rails_pulse:status`.** Reports schema, migration, and tracking/auth config state in one command, and exits 1 if anything needs action before deploying.
|
|
19
|
+
- **Schema drift guard.** `RailsPulse::SchemaCheck` detects tables or columns the running gem expects but that haven't been migrated yet, and pauses tracking (dashboard returns 503) with a clear warning instead of erroring. Disable with `config.schema_check_enabled = false`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Fixed breadcrumb links becoming protocol-relative (`//queries`) when the engine is mounted at `/`.
|
|
24
|
+
- Fixed the standalone dashboard server 404ing on its own stylesheets and scripts.
|
|
25
|
+
- Fixed asset responses using mixed-case headers, which `Rack::Lint` rejects under Rack 3.
|
|
26
|
+
- Fixed `rails_pulse_server` ignoring `RAILS_ENV`/`RACK_ENV` and always booting `rackup` in development mode.
|
|
27
|
+
- Fixed the standalone server requiring a literal `SECRET_KEY_BASE` instead of falling back to the host app's.
|
|
28
|
+
|
|
29
|
+
### Security
|
|
30
|
+
|
|
31
|
+
- Updated development dependencies mail, net-imap, and json for several CVEs (gem dev/CI only).
|
|
32
|
+
- Updated development dependencies nokogiri, loofah, and rails-html-sanitizer for several CVEs (gem dev/CI only).
|
|
33
|
+
- Updated development dependencies Rails, puma, websocket-driver, and concurrent-ruby for Dependabot's critical/high alerts. Host apps should update their own Rails to 8.1.3.1.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- `rails generate rails_pulse:upgrade` now reports unrun migrations and outstanding route backfill instead of saying everything is up to date.
|
|
38
|
+
- Cleaned up `rake test` output — no more RDoc warnings, stray blank lines, or leaked log noise.
|
|
39
|
+
|
|
10
40
|
## [0.4.0.pre.2] - 2026-09-04
|
|
11
41
|
|
|
12
42
|
### Fixed
|
|
13
43
|
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
at the first escaped quote. The `http_methods` comment embeds a `["GET","POST"]`
|
|
17
|
-
example, so it was truncated mid-escape and the trailing backslash escaped the
|
|
18
|
-
closing quote of the generated `comment:` string — the copied
|
|
19
|
-
`upgrade_rails_pulse_tables.rb` then failed to load with
|
|
20
|
-
`unterminated string meets end of file`. Comments are now matched as complete Ruby
|
|
21
|
-
string literals, decoded with `String#undump`, and re-escaped with `String#inspect`
|
|
22
|
-
when the migration is rendered.
|
|
23
|
-
|
|
24
|
-
- **Background tracking writes corrupted the host app's test database connection.**
|
|
25
|
-
Under `use_transactional_tests` Rails pins one connection per pool and hands that
|
|
26
|
-
same connection to every thread, so the tracker's writer thread interleaved its
|
|
27
|
-
INSERTs with the test's own statements on a single socket. PostgreSQL reported
|
|
28
|
-
this as `message type 0x5a arrived from server while idle` and
|
|
29
|
-
`server sent data ("D" message) without prior row description`, and suites hung
|
|
30
|
-
or failed at random. The gem's own dummy app avoided it with
|
|
31
|
-
`config.async = false if Rails.env.test?`, but the installed initializer never
|
|
32
|
-
shipped that line. The tracker now writes inline whenever the pool's connection
|
|
33
|
-
is shared across threads, and `rails generate rails_pulse:install` adds the
|
|
34
|
-
test-environment line to the initializer. Existing installs should add
|
|
35
|
-
`config.async = false if Rails.env.test?` to `config/initializers/rails_pulse.rb`.
|
|
44
|
+
- Fixed `rails generate rails_pulse:upgrade` writing a migration that failed to parse when a column comment contained an escaped quote.
|
|
45
|
+
- Fixed background tracking writes corrupting the test database connection under transactional tests. Existing installs should add `config.async = false if Rails.env.test?` to their initializer.
|
|
36
46
|
|
|
37
47
|
## [0.4.0.pre.1] - 2026-09-03
|
|
38
48
|
|
|
39
|
-
This release contains a **breaking schema change** and requires a one-time data
|
|
40
|
-
migration. Read "Upgrading from 0.3.x" below before deploying. Take a database
|
|
41
|
-
backup first: the route migration is irreversible.
|
|
49
|
+
This release contains a **breaking schema change** and requires a one-time data migration. Back up your database first — the route migration is irreversible. See "Upgrading from 0.3.x" below.
|
|
42
50
|
|
|
43
51
|
### Security
|
|
44
52
|
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
calls `protect_from_forgery with: :exception` itself instead of inheriting it from the
|
|
53
|
-
host's `default_protect_from_forgery`, which only `load_defaults 5.2+` enables. Hosts
|
|
54
|
-
on an older `load_defaults` had no CSRF protection on any engine endpoint (tags,
|
|
55
|
-
exception status, query reanalyze, session filters). Brakeman's `CheckForgerySetting`
|
|
56
|
-
is no longer skipped.
|
|
57
|
-
- **Authentication hooks fail closed.** An `authentication_method` that returned
|
|
58
|
-
`false` without rendering or redirecting used to grant access — `proc { current_user&.admin? }`
|
|
59
|
-
looked right and let every non-admin in. Literal `false` is now a 403; `nil`
|
|
60
|
-
(what the documented `unless … redirect_to … end` style returns on success) still allows.
|
|
61
|
-
- **EXPLAIN hardening.** The query analyzer now refuses SQL containing `;`, `--` or `/*`
|
|
62
|
-
anywhere (PostgreSQL's `execute` runs every statement in a string, so a captured
|
|
63
|
-
`SELECT …; UPDATE …` would have re-run the UPDATE on each page view), runs the
|
|
64
|
-
PostgreSQL EXPLAIN inside `SET LOCAL transaction_read_only = on` with a 5s
|
|
65
|
-
`statement_timeout` instead of `Timeout.timeout`, and always rolls its savepoint back.
|
|
66
|
-
- **Deployments API input is bounded.** `revision` is capped at 255 characters,
|
|
67
|
-
serialized `metadata` at 4 KB, and `started_at` may be at most one hour in the future
|
|
68
|
-
(a forged future marker would have stamped every later exception with a revision
|
|
69
|
-
that never shipped). `rails_pulse_deployments` now has a count cap
|
|
70
|
-
(`max_table_records`, default 1 000) enforced by `CleanupService`; deployments are
|
|
71
|
-
never pruned by age.
|
|
72
|
-
- **Backtrace source snippets are limited to `app/`, `lib/` and `config/routes.rb`**
|
|
73
|
-
(`.rb`, `.erb`, `.rake`, `.haml`, `.slim`, `.jbuilder`). Previously any file under
|
|
74
|
-
`Rails.root` was readable through a backtrace frame, including initializers with
|
|
75
|
-
inline keys, `config/database.yml`, and `.env`.
|
|
76
|
-
- **Standalone dashboard session cookie is `Secure` in production** (override with
|
|
77
|
-
`RAILS_PULSE_INSECURE_SESSION=1`). The rackup's "no Rails app" branch, which could
|
|
78
|
-
never boot (`NameError: uninitialized constant Rails`), is replaced with a clear
|
|
79
|
-
error; the deployment docs now give the working invocation.
|
|
53
|
+
- Job failure messages (`rails_pulse_job_runs.error_message`) are now redacted the same way exception messages are.
|
|
54
|
+
- CSRF protection is now declared directly by the engine instead of depending on the host's `load_defaults` version.
|
|
55
|
+
- Fixed `authentication_method` granting access when it returned a falsy-but-not-`false`/redirect value; it's now fail-closed.
|
|
56
|
+
- Hardened EXPLAIN analysis against SQL injection and added a statement timeout.
|
|
57
|
+
- Bounded deployment API input (revision length, metadata size, future timestamps) and capped the `rails_pulse_deployments` row count.
|
|
58
|
+
- Backtrace source snippets are now limited to `app/`, `lib/`, and `config/routes.rb`, instead of any file under `Rails.root`.
|
|
59
|
+
- The standalone dashboard's session cookie is now `Secure` in production.
|
|
80
60
|
|
|
81
61
|
### Added
|
|
82
62
|
|
|
83
|
-
- **Exception tracking
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`DETAIL:` clauses.
|
|
90
|
-
- **`track_exceptions` config option** — Gem default is `false`, so existing installs
|
|
91
|
-
do not begin capturing on upgrade; the upgrade generator inserts `false`
|
|
92
|
-
explicitly. The install template sets `true` for new apps.
|
|
93
|
-
- **`capture_exception_params` config option** — Captures request params alongside
|
|
94
|
-
each occurrence, filtered via Rails' `filter_parameters`. Occurrences whose params
|
|
95
|
-
exceed 10 KB after filtering are stored without params. Default `true`, and only
|
|
96
|
-
consulted while `track_exceptions` is on.
|
|
97
|
-
- **`exception_message_filter` config option** — an optional `->(message, exception)`
|
|
98
|
-
hook applied after the built-in redaction for app-specific patterns. If the hook
|
|
99
|
-
raises, the message is stored as `[FILTERED]` rather than unfiltered.
|
|
100
|
-
- **`authorize` config option** — a fail-closed predicate, `->(controller) { … }`
|
|
101
|
-
(or a zero-argument proc run in the controller). Anything falsy is a 403. Runs after
|
|
102
|
-
`authentication_method` when both are set, and replaces the HTTP Basic fallback on
|
|
103
|
-
its own. This is now the recommended way to gate the dashboard.
|
|
104
|
-
- **Upgrade generator syncs new initializer settings** — Appends keys (and
|
|
105
|
-
`max_table_records` entries) the host file does not already mention, without
|
|
106
|
-
rewriting existing values. Review with `git diff`.
|
|
63
|
+
- **Exception tracking.** Captures unhandled exceptions from requests and jobs, with grouping, backtraces, and redacted params in a new Exceptions tab.
|
|
64
|
+
- **`track_exceptions` config option**, off by default for existing installs and on by default for new ones.
|
|
65
|
+
- **`capture_exception_params` config option** to include filtered request params with each exception occurrence.
|
|
66
|
+
- **`exception_message_filter` config option** for app-specific redaction beyond the built-in rules.
|
|
67
|
+
- **`authorize` config option** — a fail-closed predicate for gating dashboard access; now the recommended approach.
|
|
68
|
+
- **The upgrade generator now syncs new initializer settings** into the host's config file without overwriting existing values.
|
|
107
69
|
|
|
108
70
|
### Changed
|
|
109
71
|
|
|
110
|
-
- **BREAKING — route identity is now `[controller_action, path]
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
- **A one-time backfill is mandatory.** `rails rails_pulse:migrate_routes` backfills
|
|
114
|
-
`controller_action` (from the live router, then from request history), collapses
|
|
115
|
-
historical literal paths, and merges routes sharing an action (GET+POST `/sign_in`
|
|
116
|
-
→ `sessions#new`). A schema migrate alone leaves the Action column empty. The
|
|
117
|
-
upgrade generator and the dashboard both warn when the step is outstanding.
|
|
118
|
-
- **The JavaScript bundle is 66% smaller: 2.19 MB → 759 KB (754 KB → 248 KB gzipped).**
|
|
119
|
-
ECharts is now tree-shaken — `application.js` imports from `echarts/core` and
|
|
120
|
-
registers only the series and components the dashboard uses, instead of the
|
|
121
|
-
`echarts` barrel that bundled every chart type including maps, sankey and gauge.
|
|
122
|
-
Charts outside that set must now register their module explicitly; see
|
|
123
|
-
`docs/stimulus_echarts_usage.md`. The stylesheet is 5% smaller.
|
|
72
|
+
- **BREAKING — route identity is now `[controller_action, path]`**, so different HTTP methods on the same path are tracked as distinct routes.
|
|
73
|
+
- **A one-time `rails rails_pulse:migrate_routes` backfill is required** after migrating; a schema migrate alone leaves the Action column empty.
|
|
74
|
+
- **The JavaScript bundle is 66% smaller** (2.19 MB → 759 KB) after tree-shaking ECharts.
|
|
124
75
|
|
|
125
76
|
### Removed
|
|
126
77
|
|
|
127
|
-
- **BREAKING — `rails_pulse_routes.method` is dropped
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
`ActiveRecord::IrreversibleMigration` on
|
|
134
|
-
`ChangeRailsPulseRoutesToMultiVerbModel`, and routes merged by
|
|
135
|
-
`rails_pulse:migrate_routes` are deleted with no audit trail. Recovery from a bad
|
|
136
|
-
upgrade is restore-from-backup only.
|
|
137
|
-
- **Three unreachable Stimulus controllers** — `form`, `timezone` and
|
|
138
|
-
`period_selector` — were bundled and registered despite no view referencing them.
|
|
139
|
-
`timezone` also ran a `MutationObserver` over the whole document body.
|
|
140
|
-
- **`theme.js`**, which registered a chart theme that `application.js` immediately
|
|
141
|
-
overwrote. Its UMD wrapper called `require('echarts')`, which alone would have
|
|
142
|
-
defeated the tree-shaking above.
|
|
143
|
-
- **Dead CSS**: unused css-zero ports, the removed dashboard period selector's styles,
|
|
144
|
-
and the disabled `toolbox` chart option.
|
|
145
|
-
- **`csp-test.js` is no longer shipped in the gem.** Its page is mounted only when
|
|
146
|
-
`Rails.env.local?`, so it was unreachable in a published gem. It remains in the
|
|
147
|
-
repository for development and the CSP compliance system test.
|
|
78
|
+
- **BREAKING — `rails_pulse_routes.method` is dropped**; the HTTP verb now lives on each request. Restart all processes together after migrating.
|
|
79
|
+
- **BREAKING — the route migration is irreversible.** Back up your database first.
|
|
80
|
+
- Removed three unused Stimulus controllers (`form`, `timezone`, `period_selector`).
|
|
81
|
+
- Removed `theme.js`, a chart theme that was immediately overwritten and would have defeated ECharts tree-shaking.
|
|
82
|
+
- Removed dead CSS: unused css-zero ports, old period-selector styles, and a disabled toolbox option.
|
|
83
|
+
- `csp-test.js` is no longer shipped in the published gem.
|
|
148
84
|
|
|
149
85
|
### Fixed
|
|
150
86
|
|
|
151
|
-
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
raising. Session-stored presets and performance thresholds are validated against
|
|
159
|
-
the known values.
|
|
160
|
-
- **Chart click/zoom got slower the more you switched chart tabs.** The requests,
|
|
161
|
-
routes and queries index pages attached a fresh `document` mouseup listener every
|
|
162
|
-
time a chart tab was re-selected, and only the most recent one was ever removed.
|
|
163
|
-
After N switches a single click anywhere on the page re-ran the zoom handler N
|
|
164
|
-
times. The listener is now bound once per controller.
|
|
165
|
-
- **Zooming onto the first column of a category chart reset the range.** Visible-range
|
|
166
|
-
extraction used `dataZoom.startValue || 0`, so a legitimate index of `0` fell through
|
|
167
|
-
to the full-axis fallback.
|
|
168
|
-
- **Hover popovers could throw after a table refresh.** `popover_controller` left its
|
|
169
|
-
show/hide timers running on disconnect, so a pending 700ms open could fire against
|
|
170
|
-
an element a fetch had already replaced.
|
|
171
|
-
- **The time range selector's hover border was invisible.** It referenced
|
|
172
|
-
`--color-border-emphasis`, which is defined nowhere, so the declaration was invalid
|
|
173
|
-
at computed-value time and the border fell back to `currentColor`. Its light-mode
|
|
174
|
-
rules also never applied on a first visit, because `data-color-scheme` is only set
|
|
175
|
-
after an explicit toggle.
|
|
176
|
-
- **`[popover].positioned` was declared twice** at equal specificity, so correct
|
|
177
|
-
popover placement depended on CSS file load order. Merged into one rule.
|
|
87
|
+
- Fixed tag filters not matching tags containing `_` on SQLite.
|
|
88
|
+
- Fixed various hand-edited or malformed query strings causing 500s instead of falling back to defaults.
|
|
89
|
+
- Fixed chart click/zoom handlers accumulating on every chart tab switch, slowing clicks down over time.
|
|
90
|
+
- Fixed zooming to the first column of a category chart resetting the range instead of applying it.
|
|
91
|
+
- Fixed hover popovers throwing after a table refresh replaced the underlying element.
|
|
92
|
+
- Fixed the time range selector's hover border being invisible due to an undefined CSS variable.
|
|
93
|
+
- Fixed a duplicate CSS rule that made popover placement depend on file load order.
|
|
178
94
|
- Fixed upgrading when using a separate database installation.
|
|
179
|
-
- Separate-database installs set `schema_dump: false
|
|
180
|
-
|
|
181
|
-
- Fixed `
|
|
182
|
-
dashboard assets with the host Sprockets pipeline. Precompile copies the pre-built
|
|
183
|
-
files into `public/assets` (digested, no compressor) so `config.asset_host` and
|
|
184
|
-
CDN-only CSP work; development and hosts without a pipeline still use
|
|
185
|
-
`/rails-pulse-assets/<gem-version>/...`.
|
|
186
|
-
- SQLite `schema.rb` now keeps the partial unique index on unrecognised routes
|
|
187
|
-
(`WHERE controller_action IS NULL`). A raw `CREATE INDEX` was dumped without the
|
|
188
|
-
predicate, so `db:schema:load` and parallel tests unique-constrained every path.
|
|
95
|
+
- Separate-database installs now set `schema_dump: false` so `db:migrate` doesn't dump or load `db/rails_pulse_structure.sql` (#189).
|
|
96
|
+
- Fixed `assets:precompile` OOMing on memory-constrained hosts by not registering dashboard assets with Sprockets.
|
|
97
|
+
- Fixed SQLite's `schema.rb` dropping the partial unique index on unrecognised routes, which over-constrained paths on `db:schema:load`.
|
|
189
98
|
|
|
190
99
|
### Upgrading from 0.3.x
|
|
191
100
|
|
|
@@ -209,21 +118,19 @@ once you have reviewed what is captured.
|
|
|
209
118
|
|
|
210
119
|
## [0.3.3] - 2026-06-23
|
|
211
120
|
|
|
212
|
-
- **Deployment tracking** — Record deployments via `POST /rails_pulse/deployments` or `rake rails_pulse:record_deployment[sha]
|
|
213
|
-
- **`deployment_api_token` config option** — Secures the deployments endpoint with a token header for CI/CD use
|
|
214
|
-
-
|
|
121
|
+
- **Deployment tracking** — Record deployments via `POST /rails_pulse/deployments` or `rake rails_pulse:record_deployment[sha]`, shown as marker lines on performance charts.
|
|
122
|
+
- **`deployment_api_token` config option** — Secures the deployments endpoint with a token header for CI/CD use.
|
|
123
|
+
- Charts now use a native ECharts time axis instead of a separate labels array, enabling deployment markers and better zoom behaviour.
|
|
215
124
|
|
|
216
125
|
## [0.3.0] - 2026-04-30
|
|
217
126
|
|
|
218
|
-
This is the largest release to date — a full UI overhaul
|
|
219
|
-
the dashboard.
|
|
220
|
-
|
|
221
|
-
refactored. Existing installs need to run the two new migrations. Hopefully future
|
|
222
|
-
releases will be smaller and more incremental than this one.
|
|
127
|
+
This is the largest release to date — a full UI overhaul with switchable charts and
|
|
128
|
+
automatic health status across the dashboard. Existing installs need to run two new
|
|
129
|
+
migrations.
|
|
223
130
|
|
|
224
131
|
### Added
|
|
225
132
|
|
|
226
|
-
- **New chart types across all sections** — Routes, queries, and jobs now have dedicated
|
|
133
|
+
- **New chart types across all sections** — Routes, queries, and jobs now have dedicated, switchable chart panels (percentiles, volume, error/failure rate, duration)
|
|
227
134
|
- **Dashboard health summary** — New `HealthSummary` model surfaces an overall health status and highlights routes/queries/jobs that need attention
|
|
228
135
|
- **Dashboard "Needs Attention" section** — Automatically surfaces slow routes, high error rates, and problematic jobs without manual digging
|
|
229
136
|
- **Storage pressure indicator** — Tracks and displays database storage growth so you know when to adjust retention settings
|
|
@@ -249,16 +156,16 @@ releases will be smaller and more incremental than this one.
|
|
|
249
156
|
|
|
250
157
|
### Changed
|
|
251
158
|
|
|
252
|
-
- **Dashboard redesigned** — Full
|
|
159
|
+
- **Dashboard redesigned** — Full overhaul with health summary, attention sections, and chart panels replacing the previous card layout
|
|
253
160
|
- **Routes, queries, and jobs index pages redesigned** — Consistent layout with metric cards, switchable chart tabs, and paginated tables
|
|
254
161
|
- **Metric card component updated** — Cards now support a richer data structure with trend indicators
|
|
255
|
-
- **Application controller refactored** —
|
|
256
|
-
- **`normalized_sql` column expanded to `text`** — Removes the 1000-character limit on PostgreSQL and MySQL (migration provided
|
|
162
|
+
- **Application controller refactored** — Split into focused concerns (`ChartTableConcern`, `MetricCardConcern`, `PaginationConcern`, `SessionFiltersConcern`, `TagFilterConcern`, `TimeRangeConcern`, `ZoomRangeConcern`)
|
|
163
|
+
- **`normalized_sql` column expanded to `text`** — Removes the 1000-character limit on PostgreSQL and MySQL (migration provided)
|
|
257
164
|
- **Diagnostic fields added to queries table** — New columns captured at instrumentation time
|
|
258
165
|
- **Summary job and service refactored** — Cleaner separation between job scheduling and summary calculation logic
|
|
259
166
|
- **Cleanup job refactored** — Now uses `CleanupTaskRunner` and reports statistics via `CleanupStatsReporter`
|
|
260
167
|
- **Configuration expanded** — New configuration options exposed in the initializer template
|
|
261
|
-
- **Upgrade generator simplified** —
|
|
168
|
+
- **Upgrade generator simplified** — Now delegates to `MigrationInstaller` and `ConfigInstaller`
|
|
262
169
|
- **Asset server middleware updated** — Improved asset serving reliability
|
|
263
170
|
- **Request collector middleware updated** — Performance improvements and cleaner instrumentation
|
|
264
171
|
- **Operation subscriber refactored** — Cleaner event handling and reduced complexity
|
|
@@ -269,10 +176,9 @@ releases will be smaller and more incremental than this one.
|
|
|
269
176
|
|
|
270
177
|
- `StatusHelper` removed — status rendering consolidated into model concerns and view components
|
|
271
178
|
- `ChartFormatters` helper removed — chart formatting moved into chart model classes
|
|
272
|
-
- `chart_formatters_test.rb` removed alongside the helper
|
|
273
179
|
- Slow queries and slow routes table models removed — replaced by the Needs Attention system
|
|
274
180
|
- Unused `average_response_time` and `p95_response_time` dashboard chart classes removed
|
|
275
|
-
- `requests/tables/index.rb`
|
|
181
|
+
- `requests/tables/index.rb` removed — requests index now uses a shared approach
|
|
276
182
|
- `routes/cards/average_response_times` and `routes/charts/average_response_times` removed — replaced by percentile-based equivalents
|
|
277
183
|
- `jobs/cards/average_duration` and `jobs/cards/total_jobs` removed — replaced by updated cards
|
|
278
184
|
- Timezone controller removed
|
data/README.md
CHANGED
|
@@ -75,6 +75,8 @@ Restart all processes after migrate. This release changes how routes are stored
|
|
|
75
75
|
|
|
76
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
77
|
|
|
78
|
+
To see where an install stands at any point — schema, unrun migration files, route backfill, initializer — run `rails rails_pulse:status`. It exits 1 when something needs action, so it can gate a deploy.
|
|
79
|
+
|
|
78
80
|
Exception tracking is **off** for existing installs. The generator inserts `config.track_exceptions = false`; set it to `true` after migrating to opt in:
|
|
79
81
|
|
|
80
82
|
```ruby
|
|
@@ -15,6 +15,7 @@ module RailsPulse
|
|
|
15
15
|
protect_from_forgery with: :exception
|
|
16
16
|
|
|
17
17
|
before_action :authenticate_rails_pulse_user!
|
|
18
|
+
before_action :require_current_schema!
|
|
18
19
|
before_action :set_onboarding_state
|
|
19
20
|
before_action :load_deployment_markers
|
|
20
21
|
|
|
@@ -132,6 +133,8 @@ module RailsPulse
|
|
|
132
133
|
config = RailsPulse.configuration
|
|
133
134
|
return unless config.authentication_enabled
|
|
134
135
|
|
|
136
|
+
return authenticate_standalone!(config) if RailsPulse.standalone?
|
|
137
|
+
|
|
135
138
|
if config.authentication_method.nil? && config.authorize.nil?
|
|
136
139
|
return fallback_http_basic_auth
|
|
137
140
|
end
|
|
@@ -144,7 +147,39 @@ module RailsPulse
|
|
|
144
147
|
run_authorize_predicate(config.authorize) if config.authorize
|
|
145
148
|
rescue StandardError => e
|
|
146
149
|
logger.warn "RailsPulse authentication failed: #{e.message}"
|
|
147
|
-
|
|
150
|
+
# The redirect target is a host app page; from the standalone server
|
|
151
|
+
# it would point at a route this process does not serve.
|
|
152
|
+
if RailsPulse.standalone?
|
|
153
|
+
render plain: "Forbidden", status: :forbidden
|
|
154
|
+
else
|
|
155
|
+
redirect_to config.authentication_redirect_path
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# The standalone server has the host's models but not its middleware:
|
|
160
|
+
# no host session store, Warden, or Devise helpers, and a different
|
|
161
|
+
# hostname so no shared cookie. A session-based `authentication_method`
|
|
162
|
+
# or `authorize` would raise (rescued above into a useless redirect) or,
|
|
163
|
+
# worse, be written so that it silently allows. Ignore both here; use
|
|
164
|
+
# `standalone_authentication_method` when set, else HTTP Basic.
|
|
165
|
+
def authenticate_standalone!(config)
|
|
166
|
+
hook = config.standalone_authentication_method
|
|
167
|
+
if hook.nil?
|
|
168
|
+
note_ignored_host_authentication(config)
|
|
169
|
+
return fallback_http_basic_auth
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
run_authentication_method(hook)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def note_ignored_host_authentication(config)
|
|
176
|
+
return if config.authentication_method.nil? && config.authorize.nil?
|
|
177
|
+
return if self.class.instance_variable_get(:@_rails_pulse_standalone_auth_noted)
|
|
178
|
+
|
|
179
|
+
self.class.instance_variable_set(:@_rails_pulse_standalone_auth_noted, true)
|
|
180
|
+
logger.info "RailsPulse: standalone dashboard ignores config.authentication_method / config.authorize " \
|
|
181
|
+
"(host session helpers are not available here); using HTTP Basic auth. " \
|
|
182
|
+
"Set config.standalone_authentication_method to customise."
|
|
148
183
|
end
|
|
149
184
|
|
|
150
185
|
def run_authentication_method(hook)
|
|
@@ -196,6 +231,30 @@ module RailsPulse
|
|
|
196
231
|
end
|
|
197
232
|
end
|
|
198
233
|
|
|
234
|
+
# New gem code against un-migrated tables would otherwise 500 on every
|
|
235
|
+
# page that touches a missing column. Runs after authentication so the
|
|
236
|
+
# schema report is not exposed to anonymous callers.
|
|
237
|
+
def require_current_schema!
|
|
238
|
+
return if RailsPulse::SchemaCheck.current?
|
|
239
|
+
|
|
240
|
+
RailsPulse::SchemaCheck.warn_once!
|
|
241
|
+
missing = RailsPulse::SchemaCheck.missing
|
|
242
|
+
|
|
243
|
+
respond_to do |format|
|
|
244
|
+
format.html do
|
|
245
|
+
render template: "rails_pulse/shared/schema_outdated", layout: false,
|
|
246
|
+
status: :service_unavailable, locals: { missing: missing }
|
|
247
|
+
end
|
|
248
|
+
format.any do
|
|
249
|
+
render json: {
|
|
250
|
+
error: "Rails Pulse #{RailsPulse::VERSION} requires a schema upgrade",
|
|
251
|
+
missing: missing,
|
|
252
|
+
instructions: RailsPulse::SchemaCheck::UPGRADE_INSTRUCTIONS
|
|
253
|
+
}, status: :service_unavailable
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
199
258
|
def load_deployment_markers
|
|
200
259
|
@deployment_markers = []
|
|
201
260
|
@show_deployment_markers = session_show_deployment_markers
|
|
@@ -36,10 +36,7 @@ module RailsPulse
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def finish
|
|
39
|
-
deployment = RailsPulse::Deployment
|
|
40
|
-
.where(revision: finish_params[:revision])
|
|
41
|
-
.order(started_at: :desc)
|
|
42
|
-
.first
|
|
39
|
+
deployment = RailsPulse::Deployment.latest_for_revision(finish_params[:revision])
|
|
43
40
|
|
|
44
41
|
return render json: { status: "error", error: "Deployment not found" }, status: :not_found unless deployment
|
|
45
42
|
|
|
@@ -30,13 +30,16 @@ module RailsPulse
|
|
|
30
30
|
# Only keep segments after the mount point
|
|
31
31
|
path_segments = path_segments[(mount_end_index + 1)..-1]
|
|
32
32
|
|
|
33
|
-
# Build the engine root path directly from mount segments (avoids relying on named route helper)
|
|
34
|
-
|
|
33
|
+
# Build the engine root path directly from mount segments (avoids relying on named route helper).
|
|
34
|
+
# Served at "/" (standalone server, or a host that mounts the engine at root) the prefix
|
|
35
|
+
# is empty, not "/": segments are appended as "/#{segment}", and "/" + "/queries" would be
|
|
36
|
+
# "//queries" — a protocol-relative URL that browsers resolve to a host called "queries".
|
|
37
|
+
engine_root = mount_segments.empty? ? "" : "/" + mount_segments.join("/")
|
|
35
38
|
|
|
36
39
|
# Start with the Home link
|
|
37
40
|
crumbs = [ {
|
|
38
41
|
title: "Home",
|
|
39
|
-
path: engine_root,
|
|
42
|
+
path: engine_root.empty? ? "/" : engine_root,
|
|
40
43
|
current: path_segments.empty?
|
|
41
44
|
} ]
|
|
42
45
|
|
|
@@ -18,6 +18,12 @@ module RailsPulse
|
|
|
18
18
|
scope :for_range, ->(start_time, end_time) { where(started_at: start_time..end_time) }
|
|
19
19
|
scope :recent, -> { order(started_at: :desc) }
|
|
20
20
|
|
|
21
|
+
# A revision can be deployed more than once (rollbacks, re-deploys); both
|
|
22
|
+
# the finish API endpoint and rails_pulse:finish_deployment close this row.
|
|
23
|
+
def self.latest_for_revision(revision)
|
|
24
|
+
where(revision: revision).order(started_at: :desc).first
|
|
25
|
+
end
|
|
26
|
+
|
|
21
27
|
def self.ransackable_attributes(auth_object = nil)
|
|
22
28
|
%w[id revision started_at finished_at created_at]
|
|
23
29
|
end
|
|
@@ -19,6 +19,8 @@ module RailsPulse
|
|
|
19
19
|
|
|
20
20
|
class << self
|
|
21
21
|
def capture(exception, request_url: nil, request_method: nil, request_params: nil, environment: nil, deploy_sha: nil)
|
|
22
|
+
return unless RailsPulse::SchemaCheck.tracking_allowed?
|
|
23
|
+
|
|
22
24
|
new(exception, request_url: request_url, request_method: request_method, request_params: request_params, environment: environment, deploy_sha: deploy_sha).capture
|
|
23
25
|
end
|
|
24
26
|
|
|
@@ -6,13 +6,9 @@ module RailsPulse
|
|
|
6
6
|
|
|
7
7
|
def call
|
|
8
8
|
results = { updated: 0, skipped: 0, already_set: 0, merged: 0 }
|
|
9
|
-
total = RailsPulse::Route.count
|
|
10
|
-
processed = 0
|
|
11
9
|
RailsPulse::Route.find_each do |route|
|
|
12
10
|
process_route(route, results)
|
|
13
|
-
processed += 1
|
|
14
11
|
end
|
|
15
|
-
puts "" if total > 0
|
|
16
12
|
consolidate_unrecognized_duplicates(results)
|
|
17
13
|
results
|
|
18
14
|
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<%# Rendered with layout: false from ApplicationController#require_current_schema!.
|
|
2
|
+
Mirrors the application layout's setup screen (same head, stylesheet and
|
|
3
|
+
setup-* classes) without depending on the before_actions or table reads the
|
|
4
|
+
real layout needs — the schema behind this page is, by definition, not one
|
|
5
|
+
this gem version can query. %>
|
|
6
|
+
<!DOCTYPE html>
|
|
7
|
+
<html data-color-scheme="light">
|
|
8
|
+
<head>
|
|
9
|
+
<title>Schema upgrade required — Rails Pulse</title>
|
|
10
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
11
|
+
<%= csp_meta_tag %>
|
|
12
|
+
|
|
13
|
+
<script nonce="<%= rails_pulse_csp_nonce %>">
|
|
14
|
+
(function() {
|
|
15
|
+
var storageKey = 'color-scheme';
|
|
16
|
+
var root = document.documentElement;
|
|
17
|
+
var scheme = 'light';
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
var stored = localStorage.getItem(storageKey);
|
|
21
|
+
if (stored === 'dark' || stored === 'light') {
|
|
22
|
+
scheme = stored;
|
|
23
|
+
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
24
|
+
scheme = 'dark';
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
28
|
+
scheme = 'dark';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
root.setAttribute('data-color-scheme', scheme);
|
|
33
|
+
})();
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<%# tag.link/script (not stylesheet_link_tag): middleware fallback paths must stay off asset_host. %>
|
|
37
|
+
<%= tag.link rel: "stylesheet", href: rails_pulse.asset_path("rails-pulse.css") %>
|
|
38
|
+
<%= tag.script "", src: rails_pulse.asset_path("rails-pulse-icons.js"), defer: true, nonce: rails_pulse_csp_nonce %>
|
|
39
|
+
</head>
|
|
40
|
+
|
|
41
|
+
<body class="header-layout">
|
|
42
|
+
<main id="main">
|
|
43
|
+
<div class="container flex flex-col gap">
|
|
44
|
+
<div class="setup-screen">
|
|
45
|
+
<div class="setup-screen__brand">
|
|
46
|
+
<%= tag.img src: rails_pulse.asset_path("rails-pulse-logo.png"),
|
|
47
|
+
width: 72,
|
|
48
|
+
height: 72,
|
|
49
|
+
alt: "Rails Pulse",
|
|
50
|
+
class: "setup-screen__logo" %>
|
|
51
|
+
<p class="setup-screen__brand-name">Rails Pulse</p>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="setup-banner setup-banner--negative">
|
|
55
|
+
<p class="setup-banner__title">Rails Pulse <%= RailsPulse::VERSION %> needs a schema upgrade</p>
|
|
56
|
+
<p class="setup-banner__body">
|
|
57
|
+
The gem in this process is newer than the Rails Pulse tables it is connected to.
|
|
58
|
+
Request, job and exception tracking is paused, and the dashboard is unavailable,
|
|
59
|
+
until the database is migrated. Missing from the database:
|
|
60
|
+
</p>
|
|
61
|
+
<ul class="setup-banner__body">
|
|
62
|
+
<% missing.each do |table, columns| %>
|
|
63
|
+
<li>
|
|
64
|
+
<code><%= table %></code>
|
|
65
|
+
<% if columns == [ :table ] %>
|
|
66
|
+
— table does not exist
|
|
67
|
+
<% else %>
|
|
68
|
+
— columns: <%= safe_join(columns.map { |column| tag.code(column) }, ", ") %>
|
|
69
|
+
<% end %>
|
|
70
|
+
</li>
|
|
71
|
+
<% end %>
|
|
72
|
+
</ul>
|
|
73
|
+
<p class="setup-banner__body">Run these from the application root, then restart every process (web and workers) together:</p>
|
|
74
|
+
<% RailsPulse::SchemaCheck::UPGRADE_INSTRUCTIONS.each do |instruction| %>
|
|
75
|
+
<% command, _, comment = instruction.partition("#") %>
|
|
76
|
+
<code class="setup-banner__command"><span class="setup-banner__command-prefix">$</span>
|
|
77
|
+
<%= command.strip %><% if comment.present? %>
|
|
78
|
+
<span class="setup-banner__command-prefix"># <%= comment.strip %></span><% end %></code>
|
|
79
|
+
<% end %>
|
|
80
|
+
<p class="setup-banner__body">
|
|
81
|
+
A rolling restart that leaves older processes running against the migrated schema
|
|
82
|
+
breaks tracking the other way round. Back up the database first: the route identity
|
|
83
|
+
migration is irreversible.
|
|
84
|
+
</p>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<%= link_to "View upgrade docs →",
|
|
88
|
+
"https://railspulse.com/documentation/upgrade/",
|
|
89
|
+
class: "setup-screen__docs",
|
|
90
|
+
target: "_blank",
|
|
91
|
+
rel: "noopener" %>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</main>
|
|
95
|
+
</body>
|
|
96
|
+
</html>
|
data/exe/rails_pulse_server
CHANGED
|
@@ -21,5 +21,14 @@ unless args.any? { |arg| arg.start_with?("-p") || arg == "--port" }
|
|
|
21
21
|
args.unshift("-p", port)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# rackup's own environment defaults to "development" regardless of RAILS_ENV,
|
|
25
|
+
# and in that mode it wraps the app in Rack::Lint and Rack::ShowExceptions.
|
|
26
|
+
# Follow RACK_ENV, then RAILS_ENV, so a production dashboard is served without
|
|
27
|
+
# the development middleware (an explicit -E/--env still wins).
|
|
28
|
+
rack_env = ENV["RACK_ENV"].to_s.empty? ? ENV["RAILS_ENV"] : ENV["RACK_ENV"]
|
|
29
|
+
unless rack_env.to_s.empty? || args.any? { |arg| arg.start_with?("-E") || arg == "--env" }
|
|
30
|
+
args.unshift("-E", rack_env)
|
|
31
|
+
end
|
|
32
|
+
|
|
24
33
|
# Execute rackup with the server file
|
|
25
34
|
exec("rackup", rackup_file, *args)
|
|
@@ -80,6 +80,8 @@ module RailsPulse
|
|
|
80
80
|
migrations_paths: db/rails_pulse_migrate
|
|
81
81
|
schema_dump: false
|
|
82
82
|
|
|
83
|
+
Repeat this entry for each environment (development, test, production).
|
|
84
|
+
|
|
83
85
|
schema_dump: false is required so Rails does not dump or load
|
|
84
86
|
db/rails_pulse_structure.sql. Keep database_tasks enabled so
|
|
85
87
|
rails db:migrate:rails_pulse still runs upgrades.
|