solid_queue_monitor 2.1.0 → 2.2.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.
- checksums.yaml +4 -4
- data/README.md +24 -0
- data/app/assets/javascripts/solid_queue_monitor/application.js +3 -1
- data/app/controllers/solid_queue_monitor/application_controller.rb +6 -1
- data/app/controllers/solid_queue_monitor/assets_controller.rb +4 -0
- data/app/helpers/solid_queue_monitor/application_helper.rb +17 -0
- data/app/views/layouts/solid_queue_monitor/application.html.erb +1 -0
- data/app/views/solid_queue_monitor/failed_jobs/_row.html.erb +2 -0
- data/app/views/solid_queue_monitor/failed_jobs/index.html.erb +1 -0
- data/app/views/solid_queue_monitor/jobs/_header.html.erb +3 -0
- data/app/views/solid_queue_monitor/overview/_recent_job_row.html.erb +2 -0
- data/app/views/solid_queue_monitor/queues/_job_row.html.erb +2 -0
- data/app/views/solid_queue_monitor/queues/_row.html.erb +2 -0
- data/app/views/solid_queue_monitor/queues/show.html.erb +2 -0
- data/app/views/solid_queue_monitor/scheduled_jobs/index.html.erb +1 -0
- data/app/views/solid_queue_monitor/workers/_row.html.erb +1 -0
- data/app/views/solid_queue_monitor/workers/index.html.erb +1 -1
- data/lib/generators/solid_queue_monitor/templates/initializer.rb +7 -0
- data/lib/solid_queue_monitor/version.rb +1 -1
- data/lib/solid_queue_monitor.rb +5 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f1ef08bb05ef11d7b29d511beea231cab0d1a8e1b16fc88d6c0d1fd540660b1
|
|
4
|
+
data.tar.gz: 310ff85731b0aa63432b03f909b39eb942a0eeccf59fc4956673c50561f70582
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7df976149fe809482b5a7a2299a431957174582d5134b7080942420595a469a645fae5541082a326e035b3b05fbc355f001d31eb06377b7a1275101139f735a
|
|
7
|
+
data.tar.gz: 4e32adbbba30b69d0eea3eb106d68172d8d35ae00a3ae111eb1f560337bee25708b63d5654466cd1c47e49a1d37e3e51979e2a51ac6e5412f975ad00be1a798e
|
data/README.md
CHANGED
|
@@ -123,6 +123,9 @@ SolidQueueMonitor.setup do |config|
|
|
|
123
123
|
|
|
124
124
|
# Disable the chart on the overview page to skip chart queries entirely
|
|
125
125
|
# config.show_chart = true
|
|
126
|
+
|
|
127
|
+
# Enable CSRF protection for the dashboard's destructive actions (opt-in)
|
|
128
|
+
# config.csrf_protection_enabled = false
|
|
126
129
|
end
|
|
127
130
|
|
|
128
131
|
# Optional: inherit from a host-app controller to plug into your existing auth.
|
|
@@ -140,6 +143,27 @@ If you don't need the job activity chart, disable it to skip chart queries entir
|
|
|
140
143
|
config.show_chart = false
|
|
141
144
|
```
|
|
142
145
|
|
|
146
|
+
### CSRF Protection
|
|
147
|
+
|
|
148
|
+
The dashboard's destructive actions (retry, discard, pause, resume, execute, reject, remove/prune workers) are all `POST` requests. By default CSRF protection is **disabled**, because the gem does not assume the host application has a session store (it works in API-only apps without one).
|
|
149
|
+
|
|
150
|
+
If your host app has a session store and the dashboard is mounted on the same origin, you should enable CSRF protection:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
config.csrf_protection_enabled = true
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
When enabled:
|
|
157
|
+
|
|
158
|
+
- All dashboard forms embed an `authenticity_token`, and `csrf_meta_tags` are added to the layout for JS/`fetch`-driven requests.
|
|
159
|
+
- Unverified `POST` requests are rejected by Rails' standard `verify_authenticity_token` (returns `422 Unprocessable Entity`). Safe methods (`GET`/`HEAD`) pass through.
|
|
160
|
+
|
|
161
|
+
Requirements:
|
|
162
|
+
|
|
163
|
+
- The host app has a session store configured (e.g. `config.session_store :cookie_store`).
|
|
164
|
+
- `config.api_only` is not enabled (or session middleware is otherwise present).
|
|
165
|
+
- The dashboard is mounted on the same origin as the host app, so `form_authenticity_token` works.
|
|
166
|
+
|
|
143
167
|
### Authentication
|
|
144
168
|
|
|
145
169
|
By default, Solid Queue Monitor does not require authentication to access the dashboard. This makes it easy to get started in development environments.
|
|
@@ -322,7 +322,9 @@
|
|
|
322
322
|
function bulkSubmit(action, promptMsg) {
|
|
323
323
|
var ids = checkedBoxes().map(function (checkbox) { return checkbox.value; });
|
|
324
324
|
if (ids.length === 0 || !window.confirm(promptMsg)) return;
|
|
325
|
-
|
|
325
|
+
// Only clear previously-appended job id inputs. Other hidden inputs
|
|
326
|
+
// (e.g. the CSRF authenticity_token) must be preserved.
|
|
327
|
+
Array.prototype.slice.call(form.querySelectorAll('input[type="hidden"][name="job_ids[]"]')).forEach(function (input) { input.remove(); });
|
|
326
328
|
form.action = action;
|
|
327
329
|
ids.forEach(function (id) { appendHidden('job_ids[]', id); });
|
|
328
330
|
form.submit();
|
|
@@ -13,7 +13,12 @@ module SolidQueueMonitor
|
|
|
13
13
|
|
|
14
14
|
before_action :authenticate, if: -> { SolidQueueMonitor::AuthenticationService.authentication_required? }
|
|
15
15
|
layout 'solid_queue_monitor/application'
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
# CSRF protection is opt-in (config.csrf_protection_enabled). By default the
|
|
18
|
+
# token check is skipped so the dashboard works in hosts without a session
|
|
19
|
+
# store. When the host enables it, the standard verify_authenticity_token
|
|
20
|
+
# before_action runs and unverified POSTs are rejected.
|
|
21
|
+
skip_before_action :verify_authenticity_token, unless: -> { SolidQueueMonitor.csrf_protection_enabled }
|
|
17
22
|
|
|
18
23
|
def set_flash_message(message, type)
|
|
19
24
|
# Store in instance variable for access in views
|
|
@@ -4,6 +4,10 @@ module SolidQueueMonitor
|
|
|
4
4
|
class AssetsController < ApplicationController
|
|
5
5
|
skip_before_action :authenticate, raise: false
|
|
6
6
|
|
|
7
|
+
# Public read-only assets: exempt from CSRF so the cross-origin JavaScript
|
|
8
|
+
# guard doesn't reject GETs for the JS asset when csrf_protection_enabled.
|
|
9
|
+
skip_forgery_protection
|
|
10
|
+
|
|
7
11
|
MIME_TYPES = { '.css' => 'text/css', '.js' => 'application/javascript' }.freeze
|
|
8
12
|
FINGERPRINT_PATTERN = /\A(?<base>[A-Za-z0-9_]+)-(?<hash>[a-f0-9]+)(?<ext>\.css|\.js)\z/
|
|
9
13
|
|
|
@@ -25,6 +25,23 @@ module SolidQueueMonitor
|
|
|
25
25
|
type.to_s == 'success' ? 'message-success' : 'message-error'
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# Hidden authenticity_token field for raw HTML POST forms.
|
|
29
|
+
# Renders nothing unless CSRF protection is enabled, so hosts without a
|
|
30
|
+
# session store are unaffected (form_authenticity_token needs a session).
|
|
31
|
+
def csrf_token_field_if_enabled
|
|
32
|
+
return ''.html_safe unless SolidQueueMonitor.csrf_protection_enabled
|
|
33
|
+
|
|
34
|
+
hidden_field_tag(:authenticity_token, form_authenticity_token)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# CSRF meta tags for JS/fetch-driven POSTs (defense in depth).
|
|
38
|
+
# Only emitted when CSRF protection is enabled, for the same reason.
|
|
39
|
+
def csrf_meta_tags_if_enabled
|
|
40
|
+
return ''.html_safe unless SolidQueueMonitor.csrf_protection_enabled
|
|
41
|
+
|
|
42
|
+
csrf_meta_tags
|
|
43
|
+
end
|
|
44
|
+
|
|
28
45
|
def queue_link(queue_name, css_class: nil)
|
|
29
46
|
return '-' if queue_name.blank?
|
|
30
47
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<title>Solid Queue Monitor - <%= content_for?(:title) ? yield(:title) : 'Dashboard' %></title>
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<%= csrf_meta_tags_if_enabled %>
|
|
7
8
|
<%= stylesheet_link_tag asset_url_for('application.css'), nonce: content_security_policy_nonce %>
|
|
8
9
|
</head>
|
|
9
10
|
<body class="solid_queue_monitor"
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
<td class="actions-cell">
|
|
14
14
|
<div class="job-actions">
|
|
15
15
|
<form method="post" action="<%= retry_failed_job_path(id: job.id) %>" class="inline-form">
|
|
16
|
+
<%= csrf_token_field_if_enabled %>
|
|
16
17
|
<button type="submit" class="action-button retry-button">Retry</button>
|
|
17
18
|
</form>
|
|
18
19
|
<form method="post"
|
|
19
20
|
action="<%= discard_failed_job_path(id: job.id) %>"
|
|
20
21
|
class="inline-form"
|
|
21
22
|
data-confirm="Are you sure you want to discard this job?">
|
|
23
|
+
<%= csrf_token_field_if_enabled %>
|
|
22
24
|
<button type="submit" class="action-button discard-button">Discard</button>
|
|
23
25
|
</form>
|
|
24
26
|
</div>
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
17
|
<form method="post" id="failed-jobs-form">
|
|
18
|
+
<%= csrf_token_field_if_enabled %>
|
|
18
19
|
<% columns = [
|
|
19
20
|
{ sort_key: nil, label: tag.input(type: 'checkbox', id: 'select-all', class: 'select-all-checkbox') },
|
|
20
21
|
{ sort_key: :class_name, label: 'Job' },
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
<div class="job-actions">
|
|
16
16
|
<% if @failed_execution %>
|
|
17
17
|
<form action="<%= retry_failed_job_path(id: @failed_execution.id) %>" method="post" class="inline-form">
|
|
18
|
+
<%= csrf_token_field_if_enabled %>
|
|
18
19
|
<input type="hidden" name="redirect_to" value="<%= job_path(@job) %>">
|
|
19
20
|
<button type="submit" class="action-button retry-button">Retry</button>
|
|
20
21
|
</form>
|
|
@@ -22,12 +23,14 @@
|
|
|
22
23
|
method="post"
|
|
23
24
|
class="inline-form"
|
|
24
25
|
data-confirm="Are you sure you want to discard this job?">
|
|
26
|
+
<%= csrf_token_field_if_enabled %>
|
|
25
27
|
<input type="hidden" name="redirect_to" value="<%= failed_jobs_path %>">
|
|
26
28
|
<button type="submit" class="action-button discard-button">Discard</button>
|
|
27
29
|
</form>
|
|
28
30
|
<% end %>
|
|
29
31
|
<% if @scheduled_execution %>
|
|
30
32
|
<form action="<%= execute_scheduled_job_path(id: @scheduled_execution.id) %>" method="post" class="inline-form">
|
|
33
|
+
<%= csrf_token_field_if_enabled %>
|
|
31
34
|
<input type="hidden" name="redirect_to" value="<%= scheduled_jobs_path %>">
|
|
32
35
|
<button type="submit" class="action-button retry-button">Execute Now</button>
|
|
33
36
|
</form>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<% if failed_execution %>
|
|
11
11
|
<div class="job-actions">
|
|
12
12
|
<form method="post" action="<%= retry_failed_job_path(id: failed_execution.id) %>" class="inline-form">
|
|
13
|
+
<%= csrf_token_field_if_enabled %>
|
|
13
14
|
<input type="hidden" name="redirect_to" value="<%= root_path %>">
|
|
14
15
|
<button type="submit" class="action-button retry-button">Retry</button>
|
|
15
16
|
</form>
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
action="<%= discard_failed_job_path(id: failed_execution.id) %>"
|
|
18
19
|
class="inline-form"
|
|
19
20
|
data-confirm="Are you sure you want to discard this job?">
|
|
21
|
+
<%= csrf_token_field_if_enabled %>
|
|
20
22
|
<input type="hidden" name="redirect_to" value="<%= root_path %>">
|
|
21
23
|
<button type="submit" class="action-button discard-button">Discard</button>
|
|
22
24
|
</form>
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
<% if failed_execution %>
|
|
11
11
|
<div class="job-actions">
|
|
12
12
|
<form method="post" action="<%= retry_failed_job_path(id: failed_execution.id) %>" class="inline-form">
|
|
13
|
+
<%= csrf_token_field_if_enabled %>
|
|
13
14
|
<input type="hidden" name="redirect_to" value="<%= queue_details_path(queue_name: @queue_name) %>">
|
|
14
15
|
<button type="submit" class="action-button retry-button">Retry</button>
|
|
15
16
|
</form>
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
action="<%= discard_failed_job_path(id: failed_execution.id) %>"
|
|
18
19
|
class="inline-form"
|
|
19
20
|
data-confirm="Are you sure you want to discard this job?">
|
|
21
|
+
<%= csrf_token_field_if_enabled %>
|
|
20
22
|
<input type="hidden" name="redirect_to" value="<%= queue_details_path(queue_name: @queue_name) %>">
|
|
21
23
|
<button type="submit" class="action-button discard-button">Discard</button>
|
|
22
24
|
</form>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
<td class="actions-cell">
|
|
18
18
|
<% if paused %>
|
|
19
19
|
<form action="<%= resume_queue_path %>" method="post" class="inline-form">
|
|
20
|
+
<%= csrf_token_field_if_enabled %>
|
|
20
21
|
<input type="hidden" name="queue_name" value="<%= queue_name %>">
|
|
21
22
|
<button type="submit" class="action-button resume-button" title="Resume queue processing">Resume</button>
|
|
22
23
|
</form>
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
method="post"
|
|
26
27
|
class="inline-form"
|
|
27
28
|
data-confirm="Are you sure you want to pause the <%= queue_name %> queue? Workers will stop processing jobs from this queue.">
|
|
29
|
+
<%= csrf_token_field_if_enabled %>
|
|
28
30
|
<input type="hidden" name="queue_name" value="<%= queue_name %>">
|
|
29
31
|
<button type="submit" class="action-button pause-button" title="Pause queue processing">Pause</button>
|
|
30
32
|
</form>
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
<div class="section-header-right">
|
|
12
12
|
<% if @paused %>
|
|
13
13
|
<form action="<%= resume_queue_path %>" method="post" class="inline-form">
|
|
14
|
+
<%= csrf_token_field_if_enabled %>
|
|
14
15
|
<input type="hidden" name="queue_name" value="<%= @queue_name %>">
|
|
15
16
|
<input type="hidden" name="redirect_to" value="<%= queue_details_path(queue_name: @queue_name) %>">
|
|
16
17
|
<button type="submit" class="action-button resume-button">Resume Queue</button>
|
|
17
18
|
</form>
|
|
18
19
|
<% else %>
|
|
19
20
|
<form action="<%= pause_queue_path %>" method="post" class="inline-form" data-confirm="Are you sure you want to pause this queue?">
|
|
21
|
+
<%= csrf_token_field_if_enabled %>
|
|
20
22
|
<input type="hidden" name="queue_name" value="<%= @queue_name %>">
|
|
21
23
|
<input type="hidden" name="redirect_to" value="<%= queue_details_path(queue_name: @queue_name) %>">
|
|
22
24
|
<button type="submit" class="action-button pause-button">Pause Queue</button>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
method="post"
|
|
14
14
|
class="inline-form"
|
|
15
15
|
data-confirm="Remove this dead process from the registry?">
|
|
16
|
+
<%= csrf_token_field_if_enabled %>
|
|
16
17
|
<button type="submit" class="action-button discard-button" title="Remove dead process">Remove</button>
|
|
17
18
|
</form>
|
|
18
19
|
<% else %>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
data-confirm="Remove all <%= @summary[:dead] %> dead process<%= suffix %>? This will clean up processes that have stopped sending heartbeats.">
|
|
25
25
|
Prune all
|
|
26
26
|
</a>
|
|
27
|
-
<form id="prune-all-form" action="<%= prune_workers_path %>" method="post" class="is-hidden"
|
|
27
|
+
<form id="prune-all-form" action="<%= prune_workers_path %>" method="post" class="is-hidden"><%= csrf_token_field_if_enabled %></form>
|
|
28
28
|
<% end %>
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
@@ -27,4 +27,11 @@ SolidQueueMonitor.setup do |config|
|
|
|
27
27
|
|
|
28
28
|
# Disable the chart on the overview page to skip chart queries entirely.
|
|
29
29
|
# config.show_chart = true
|
|
30
|
+
|
|
31
|
+
# Enable CSRF protection for the dashboard's destructive POST actions.
|
|
32
|
+
# Disabled by default for backward compatibility. Requires the host app to
|
|
33
|
+
# have a session store (e.g. cookie_store) and the dashboard mounted on the
|
|
34
|
+
# same origin. When enabled, all dashboard forms embed an authenticity token
|
|
35
|
+
# and unverified POSTs are rejected.
|
|
36
|
+
# config.csrf_protection_enabled = false
|
|
30
37
|
end
|
data/lib/solid_queue_monitor.rb
CHANGED
|
@@ -11,7 +11,8 @@ module SolidQueueMonitor
|
|
|
11
11
|
class << self
|
|
12
12
|
attr_writer :username, :password, :base_controller_class
|
|
13
13
|
attr_accessor :jobs_per_page, :authentication_enabled,
|
|
14
|
-
:auto_refresh_enabled, :auto_refresh_interval, :show_chart
|
|
14
|
+
:auto_refresh_enabled, :auto_refresh_interval, :show_chart,
|
|
15
|
+
:csrf_protection_enabled
|
|
15
16
|
|
|
16
17
|
def username
|
|
17
18
|
resolve_value(@username)
|
|
@@ -39,6 +40,9 @@ module SolidQueueMonitor
|
|
|
39
40
|
@auto_refresh_enabled = true
|
|
40
41
|
@auto_refresh_interval = 30 # seconds
|
|
41
42
|
@show_chart = true
|
|
43
|
+
# Disabled by default for backward compatibility: enabling CSRF protection
|
|
44
|
+
# requires a session-backed host app, which the gem does not assume.
|
|
45
|
+
@csrf_protection_enabled = false
|
|
42
46
|
|
|
43
47
|
def self.setup
|
|
44
48
|
yield self
|