solid_queue_monitor 0.4.0 → 0.5.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 +2 -1
- data/app/controllers/solid_queue_monitor/application_controller.rb +11 -2
- data/app/controllers/solid_queue_monitor/base_controller.rb +15 -7
- data/app/controllers/solid_queue_monitor/queues_controller.rb +18 -1
- data/app/presenters/solid_queue_monitor/queues_presenter.rb +45 -6
- data/app/services/solid_queue_monitor/queue_pause_service.rb +34 -0
- data/app/services/solid_queue_monitor/stylesheet_generator.rb +24 -0
- data/config/database.yml +3 -0
- data/config/routes.rb +7 -1
- data/lib/solid_queue_monitor/engine.rb +5 -0
- data/lib/solid_queue_monitor/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2597291a879f3f6720e783c6aae3fa203434a67652ba24c5e65a6fdd87081120
|
|
4
|
+
data.tar.gz: d01e77ac3c412bf2fcdab1c4f1a02aa072786f7d88ec42798758615159ddc67c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f95324749998dd5c7a6e0598a25edd14f82bbc32610499dcf688592729f4cbf805bf3ac8da8ad3f776c0dc3231ebaa6fa710fac5537806553cc3cc50f3a7d66
|
|
7
|
+
data.tar.gz: be751aaa21b617d1ba1f09eb473a9bbd83eeccfbe79c918040fe2df420c76258b012fad5c26eefa1aaaafe50e62fdf6d566b08469042006a501d9f354cd49658
|
data/README.md
CHANGED
|
@@ -21,7 +21,8 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
|
|
|
21
21
|
- **Scheduled Jobs**: See upcoming jobs scheduled for future execution with ability to execute immediately or reject permanently
|
|
22
22
|
- **Recurring Jobs**: Manage periodic jobs that run on a schedule
|
|
23
23
|
- **Failed Jobs**: Track and debug failed jobs, with the ability to retry or discard them
|
|
24
|
-
- **Queue Management**: View and filter jobs by queue
|
|
24
|
+
- **Queue Management**: View and filter jobs by queue with pause/resume controls
|
|
25
|
+
- **Pause/Resume Queues**: Temporarily stop processing jobs on specific queues for incident response
|
|
25
26
|
- **Advanced Job Filtering**: Filter jobs by class name, queue, status, and job arguments
|
|
26
27
|
- **Quick Actions**: Retry or discard failed jobs, execute or reject scheduled jobs directly from any view
|
|
27
28
|
- **Performance Optimized**: Designed for high-volume applications with smart pagination
|
|
@@ -10,8 +10,17 @@ module SolidQueueMonitor
|
|
|
10
10
|
skip_before_action :verify_authenticity_token
|
|
11
11
|
|
|
12
12
|
def set_flash_message(message, type)
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# Store in instance variable for access in views
|
|
14
|
+
@flash_message = message
|
|
15
|
+
@flash_type = type
|
|
16
|
+
|
|
17
|
+
# Try to use Rails flash if available
|
|
18
|
+
begin
|
|
19
|
+
flash[:notice] = message if type == :success
|
|
20
|
+
flash[:alert] = message if type == :error
|
|
21
|
+
rescue StandardError
|
|
22
|
+
# Flash not available (e.g., no session middleware)
|
|
23
|
+
end
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
private
|
|
@@ -7,13 +7,21 @@ module SolidQueueMonitor
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def render_page(title, content)
|
|
10
|
-
# Get flash message from session
|
|
11
|
-
message =
|
|
12
|
-
message_type =
|
|
13
|
-
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
# Get flash message from instance variable (set by set_flash_message) or session
|
|
11
|
+
message = @flash_message
|
|
12
|
+
message_type = @flash_type
|
|
13
|
+
|
|
14
|
+
# Try to get from session as fallback, but don't fail if session unavailable
|
|
15
|
+
begin
|
|
16
|
+
message ||= session[:flash_message]
|
|
17
|
+
message_type ||= session[:flash_type]
|
|
18
|
+
|
|
19
|
+
# Clear the flash message from session after using it
|
|
20
|
+
session.delete(:flash_message) if message
|
|
21
|
+
session.delete(:flash_type) if message_type
|
|
22
|
+
rescue StandardError
|
|
23
|
+
# Session not available (e.g., no session middleware in tests)
|
|
24
|
+
end
|
|
17
25
|
|
|
18
26
|
html = SolidQueueMonitor::HtmlGenerator.new(
|
|
19
27
|
title: title,
|
|
@@ -6,8 +6,25 @@ module SolidQueueMonitor
|
|
|
6
6
|
@queues = SolidQueue::Job.group(:queue_name)
|
|
7
7
|
.select('queue_name, COUNT(*) as job_count')
|
|
8
8
|
.order('job_count DESC')
|
|
9
|
+
@paused_queues = QueuePauseService.paused_queues
|
|
9
10
|
|
|
10
|
-
render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues).render)
|
|
11
|
+
render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues, @paused_queues).render)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pause
|
|
15
|
+
queue_name = params[:queue_name]
|
|
16
|
+
result = QueuePauseService.new(queue_name).pause
|
|
17
|
+
|
|
18
|
+
set_flash_message(result[:message], result[:success] ? 'success' : 'error')
|
|
19
|
+
redirect_to queues_path
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def resume
|
|
23
|
+
queue_name = params[:queue_name]
|
|
24
|
+
result = QueuePauseService.new(queue_name).resume
|
|
25
|
+
|
|
26
|
+
set_flash_message(result[:message], result[:success] ? 'success' : 'error')
|
|
27
|
+
redirect_to queues_path
|
|
11
28
|
end
|
|
12
29
|
end
|
|
13
30
|
end
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class QueuesPresenter < BasePresenter
|
|
5
|
-
def initialize(records)
|
|
5
|
+
def initialize(records, paused_queues = [])
|
|
6
6
|
@records = records
|
|
7
|
+
@paused_queues = paused_queues
|
|
7
8
|
end
|
|
8
9
|
|
|
9
10
|
def render
|
|
@@ -19,10 +20,12 @@ module SolidQueueMonitor
|
|
|
19
20
|
<thead>
|
|
20
21
|
<tr>
|
|
21
22
|
<th>Queue Name</th>
|
|
23
|
+
<th>Status</th>
|
|
22
24
|
<th>Total Jobs</th>
|
|
23
25
|
<th>Ready Jobs</th>
|
|
24
26
|
<th>Scheduled Jobs</th>
|
|
25
27
|
<th>Failed Jobs</th>
|
|
28
|
+
<th>Actions</th>
|
|
26
29
|
</tr>
|
|
27
30
|
</thead>
|
|
28
31
|
<tbody>
|
|
@@ -34,17 +37,53 @@ module SolidQueueMonitor
|
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
def generate_row(queue)
|
|
40
|
+
queue_name = queue.queue_name || 'default'
|
|
41
|
+
paused = @paused_queues.include?(queue_name)
|
|
42
|
+
|
|
37
43
|
<<-HTML
|
|
38
|
-
<tr>
|
|
39
|
-
<td>#{
|
|
44
|
+
<tr class="#{paused ? 'queue-paused' : ''}">
|
|
45
|
+
<td>#{queue_name}</td>
|
|
46
|
+
<td>#{status_badge(paused)}</td>
|
|
40
47
|
<td>#{queue.job_count}</td>
|
|
41
|
-
<td>#{ready_jobs_count(
|
|
42
|
-
<td>#{scheduled_jobs_count(
|
|
43
|
-
<td>#{failed_jobs_count(
|
|
48
|
+
<td>#{ready_jobs_count(queue_name)}</td>
|
|
49
|
+
<td>#{scheduled_jobs_count(queue_name)}</td>
|
|
50
|
+
<td>#{failed_jobs_count(queue_name)}</td>
|
|
51
|
+
<td class="actions-cell">#{action_button(queue_name, paused)}</td>
|
|
44
52
|
</tr>
|
|
45
53
|
HTML
|
|
46
54
|
end
|
|
47
55
|
|
|
56
|
+
def status_badge(paused)
|
|
57
|
+
if paused
|
|
58
|
+
'<span class="status-badge status-paused">Paused</span>'
|
|
59
|
+
else
|
|
60
|
+
'<span class="status-badge status-active">Active</span>'
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def action_button(queue_name, paused)
|
|
65
|
+
if paused
|
|
66
|
+
<<-HTML
|
|
67
|
+
<form action="#{resume_queue_path}" method="post" class="inline-form">
|
|
68
|
+
<input type="hidden" name="queue_name" value="#{queue_name}">
|
|
69
|
+
<button type="submit" class="action-button resume-button" title="Resume queue processing">
|
|
70
|
+
Resume
|
|
71
|
+
</button>
|
|
72
|
+
</form>
|
|
73
|
+
HTML
|
|
74
|
+
else
|
|
75
|
+
<<-HTML
|
|
76
|
+
<form action="#{pause_queue_path}" method="post" class="inline-form"
|
|
77
|
+
onsubmit="return confirm('Are you sure you want to pause the #{queue_name} queue? Workers will stop processing jobs from this queue.');">
|
|
78
|
+
<input type="hidden" name="queue_name" value="#{queue_name}">
|
|
79
|
+
<button type="submit" class="action-button pause-button" title="Pause queue processing">
|
|
80
|
+
Pause
|
|
81
|
+
</button>
|
|
82
|
+
</form>
|
|
83
|
+
HTML
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
48
87
|
def ready_jobs_count(queue_name)
|
|
49
88
|
SolidQueue::ReadyExecution.where(queue_name: queue_name).count
|
|
50
89
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueMonitor
|
|
4
|
+
class QueuePauseService
|
|
5
|
+
delegate :paused?, to: :@queue
|
|
6
|
+
|
|
7
|
+
def initialize(queue_name)
|
|
8
|
+
@queue_name = queue_name
|
|
9
|
+
@queue = SolidQueue::Queue.new(queue_name)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def pause
|
|
13
|
+
return { success: false, message: "Queue '#{@queue_name}' is already paused" } if paused?
|
|
14
|
+
|
|
15
|
+
@queue.pause
|
|
16
|
+
{ success: true, message: "Queue '#{@queue_name}' has been paused" }
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
{ success: false, message: "Failed to pause queue: #{e.message}" }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def resume
|
|
22
|
+
return { success: false, message: "Queue '#{@queue_name}' is not paused" } unless paused?
|
|
23
|
+
|
|
24
|
+
@queue.resume
|
|
25
|
+
{ success: true, message: "Queue '#{@queue_name}' has been resumed" }
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
{ success: false, message: "Failed to resume queue: #{e.message}" }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.paused_queues
|
|
31
|
+
SolidQueue::Pause.pluck(:queue_name)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -182,6 +182,30 @@ module SolidQueueMonitor
|
|
|
182
182
|
.solid_queue_monitor .status-failed { background: #fee2e2; color: #991b1b; }
|
|
183
183
|
.solid_queue_monitor .status-scheduled { background: #dbeafe; color: #1e40af; }
|
|
184
184
|
.solid_queue_monitor .status-pending { background: #f3f4f6; color: #374151; }
|
|
185
|
+
.solid_queue_monitor .status-active { background: #d1fae5; color: #065f46; }
|
|
186
|
+
.solid_queue_monitor .status-paused { background: #fef3c7; color: #92400e; }
|
|
187
|
+
|
|
188
|
+
.solid_queue_monitor .queue-paused {
|
|
189
|
+
background-color: #fffbeb;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.solid_queue_monitor .pause-button {
|
|
193
|
+
background: #f59e0b;
|
|
194
|
+
color: white;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.solid_queue_monitor .pause-button:hover {
|
|
198
|
+
background: #d97706;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.solid_queue_monitor .resume-button {
|
|
202
|
+
background: #10b981;
|
|
203
|
+
color: white;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.solid_queue_monitor .resume-button:hover {
|
|
207
|
+
background: #059669;
|
|
208
|
+
}
|
|
185
209
|
|
|
186
210
|
.solid_queue_monitor .execute-btn {
|
|
187
211
|
background: var(--primary-color);
|
data/config/database.yml
ADDED
data/config/routes.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Guard against multiple loads of routes file in test environment
|
|
3
4
|
SolidQueueMonitor::Engine.routes.draw do
|
|
4
|
-
|
|
5
|
+
return if SolidQueueMonitor::Engine.routes.routes.any? { |r| r.name == 'root' }
|
|
6
|
+
|
|
7
|
+
root to: 'overview#index'
|
|
5
8
|
|
|
6
9
|
resources :ready_jobs, only: [:index]
|
|
7
10
|
resources :scheduled_jobs, only: [:index]
|
|
@@ -17,4 +20,7 @@ SolidQueueMonitor::Engine.routes.draw do
|
|
|
17
20
|
post 'discard_failed_job/:id', to: 'failed_jobs#discard', as: :discard_failed_job
|
|
18
21
|
post 'retry_failed_jobs', to: 'failed_jobs#retry_all', as: :retry_failed_jobs
|
|
19
22
|
post 'discard_failed_jobs', to: 'failed_jobs#discard_all', as: :discard_failed_jobs
|
|
23
|
+
|
|
24
|
+
post 'pause_queue', to: 'queues#pause', as: :pause_queue
|
|
25
|
+
post 'resume_queue', to: 'queues#resume', as: :resume_queue
|
|
20
26
|
end
|
|
@@ -9,6 +9,11 @@ module SolidQueueMonitor
|
|
|
9
9
|
# Optional: Add eager loading for production
|
|
10
10
|
config.eager_load_paths << root.join('app', 'services')
|
|
11
11
|
|
|
12
|
+
# Ensure session middleware is available
|
|
13
|
+
initializer 'solid_queue_monitor.middleware' do |app|
|
|
14
|
+
app.config.session_store :cookie_store, key: '_solid_queue_monitor_session' unless app.config.session_store
|
|
15
|
+
end
|
|
16
|
+
|
|
12
17
|
initializer 'solid_queue_monitor.assets' do |app|
|
|
13
18
|
# Optional: Add assets if needed
|
|
14
19
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solid_queue_monitor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vishal Sadriya
|
|
@@ -71,10 +71,12 @@ files:
|
|
|
71
71
|
- app/services/solid_queue_monitor/failed_job_service.rb
|
|
72
72
|
- app/services/solid_queue_monitor/html_generator.rb
|
|
73
73
|
- app/services/solid_queue_monitor/pagination_service.rb
|
|
74
|
+
- app/services/solid_queue_monitor/queue_pause_service.rb
|
|
74
75
|
- app/services/solid_queue_monitor/reject_job_service.rb
|
|
75
76
|
- app/services/solid_queue_monitor/stats_calculator.rb
|
|
76
77
|
- app/services/solid_queue_monitor/status_calculator.rb
|
|
77
78
|
- app/services/solid_queue_monitor/stylesheet_generator.rb
|
|
79
|
+
- config/database.yml
|
|
78
80
|
- config/initializers/solid_queue_monitor.rb
|
|
79
81
|
- config/routes.rb
|
|
80
82
|
- lib/generators/solid_queue_monitor/install_generator.rb
|