solid_queue_monitor 0.3.0 → 0.3.2
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/LICENSE +21 -0
- data/README.md +8 -8
- data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +11 -0
- data/app/presenters/solid_queue_monitor/base_presenter.rb +31 -15
- data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +38 -9
- data/app/services/solid_queue_monitor/reject_job_service.rb +49 -0
- data/app/services/solid_queue_monitor/stylesheet_generator.rb +45 -1
- data/config/routes.rb +1 -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: 76559272501afef3ca73a5f8739a0d6bea2c7c3dc5fc4a3153ed9890f5a8f0b5
|
4
|
+
data.tar.gz: efcfd9438981bc2f35a8925c6d2b08120ec490441e8f66959202c5d78dff7990
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6d8a0d27f8770f9c7ebf838e2f7e78ac72afbdf900ff5718c98861bfb25ab151250a5f36be58edcdcaa029f82d68118e0f5aa102d12c6054289e428f83069d7
|
7
|
+
data.tar.gz: 0750abe0b179438a53c8519fad9219247850de0fb661f57dd9800eb120446c4f5cea3d128ffd8b310842c0f5334c22c6f2c3db800ff46a2ffa66564b1071893d
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Vishal Sadriya
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -18,12 +18,12 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
|
|
18
18
|
- **Dashboard Overview**: Get a quick snapshot of your queue's health with statistics on all job types
|
19
19
|
- **Ready Jobs**: View jobs that are ready to be executed
|
20
20
|
- **In Progress Jobs**: Monitor jobs currently being processed by workers
|
21
|
-
- **Scheduled Jobs**: See upcoming jobs scheduled for future execution
|
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
24
|
- **Queue Management**: View and filter jobs by queue
|
25
25
|
- **Advanced Job Filtering**: Filter jobs by class name, queue, status, and job arguments
|
26
|
-
- **Quick Actions**: Retry or discard failed jobs directly from any view
|
26
|
+
- **Quick Actions**: Retry or discard failed jobs, execute or reject scheduled jobs directly from any view
|
27
27
|
- **Performance Optimized**: Designed for high-volume applications with smart pagination
|
28
28
|
- **Optional Authentication**: Secure your dashboard with HTTP Basic Authentication
|
29
29
|
- **Responsive Design**: Works on desktop and mobile devices
|
@@ -33,18 +33,18 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
|
|
33
33
|
|
34
34
|
### Dashboard Overview
|
35
35
|
|
36
|
-

|
37
37
|
|
38
38
|
### Failed Jobs
|
39
39
|
|
40
|
-

|
41
41
|
|
42
42
|
## Installation
|
43
43
|
|
44
44
|
Add this line to your application's Gemfile:
|
45
45
|
|
46
46
|
```ruby
|
47
|
-
gem 'solid_queue_monitor', '~> 0.3.
|
47
|
+
gem 'solid_queue_monitor', '~> 0.3.2'
|
48
48
|
```
|
49
49
|
|
50
50
|
Then execute:
|
@@ -103,9 +103,9 @@ The dashboard provides several views:
|
|
103
103
|
|
104
104
|
- **Overview**: Shows statistics and recent jobs
|
105
105
|
- **Ready Jobs**: Jobs that are ready to be executed
|
106
|
-
- **Scheduled Jobs**: Jobs scheduled for future execution
|
106
|
+
- **Scheduled Jobs**: Jobs scheduled for future execution with execute and reject actions
|
107
107
|
- **Recurring Jobs**: Jobs that run on a recurring schedule
|
108
|
-
- **Failed Jobs**: Jobs that have failed with error details
|
108
|
+
- **Failed Jobs**: Jobs that have failed with error details and retry/discard actions
|
109
109
|
- **Queues**: Distribution of jobs across different queues
|
110
110
|
|
111
111
|
### API-only Applications
|
@@ -127,7 +127,7 @@ This makes it easy to find specific jobs when debugging issues in your applicati
|
|
127
127
|
|
128
128
|
- **Production Monitoring**: Keep an eye on your background job processing in production environments
|
129
129
|
- **Debugging**: Quickly identify and troubleshoot failed jobs
|
130
|
-
- **Job Management**: Execute scheduled jobs on demand
|
130
|
+
- **Job Management**: Execute scheduled jobs on demand or reject unwanted jobs permanently
|
131
131
|
- **Performance Analysis**: Track job distribution and identify bottlenecks
|
132
132
|
- **DevOps Integration**: Easily integrate with your monitoring stack
|
133
133
|
|
@@ -21,5 +21,16 @@ module SolidQueueMonitor
|
|
21
21
|
end
|
22
22
|
redirect_to scheduled_jobs_path
|
23
23
|
end
|
24
|
+
|
25
|
+
def reject_all
|
26
|
+
result = SolidQueueMonitor::RejectJobService.new.reject_many(params[:job_ids])
|
27
|
+
|
28
|
+
if result[:success]
|
29
|
+
set_flash_message(result[:message], 'success')
|
30
|
+
else
|
31
|
+
set_flash_message(result[:message], 'error')
|
32
|
+
end
|
33
|
+
redirect_to scheduled_jobs_path
|
34
|
+
end
|
24
35
|
end
|
25
36
|
end
|
@@ -81,15 +81,24 @@ module SolidQueueMonitor
|
|
81
81
|
def format_arguments(arguments)
|
82
82
|
return '-' if arguments.blank?
|
83
83
|
|
84
|
-
#
|
85
|
-
if arguments.is_a?(Hash) && arguments['arguments'].present?
|
86
|
-
|
87
|
-
|
88
|
-
|
84
|
+
# Extract and format the arguments more cleanly
|
85
|
+
formatted_args = if arguments.is_a?(Hash) && arguments['arguments'].present?
|
86
|
+
format_job_arguments(arguments)
|
87
|
+
elsif arguments.is_a?(Array) && arguments.length == 1 && arguments[0].is_a?(Hash) && arguments[0]['arguments'].present?
|
88
|
+
format_job_arguments(arguments[0])
|
89
|
+
else
|
90
|
+
arguments.inspect
|
91
|
+
end
|
92
|
+
|
93
|
+
if formatted_args.length <= 50
|
94
|
+
"<code class='args-single-line'>#{formatted_args}</code>"
|
95
|
+
else
|
96
|
+
<<-HTML
|
97
|
+
<div class="args-container">
|
98
|
+
<code class="args-content">#{formatted_args}</code>
|
99
|
+
</div>
|
100
|
+
HTML
|
89
101
|
end
|
90
|
-
|
91
|
-
# For regular arguments format
|
92
|
-
"<code>#{arguments.inspect}</code>"
|
93
102
|
end
|
94
103
|
|
95
104
|
def format_hash(hash)
|
@@ -102,18 +111,14 @@ module SolidQueueMonitor
|
|
102
111
|
"<code>#{formatted}</code>"
|
103
112
|
end
|
104
113
|
|
105
|
-
# Helper method to get the current request path
|
106
114
|
def request_path
|
107
|
-
# Try to get the current path from the controller's request
|
108
115
|
if defined?(controller) && controller.respond_to?(:request)
|
109
116
|
controller.request.path
|
110
117
|
else
|
111
|
-
# Fallback to a default path if we can't get the current path
|
112
118
|
'/solid_queue'
|
113
119
|
end
|
114
120
|
end
|
115
121
|
|
116
|
-
# Helper method to get the mount point of the engine
|
117
122
|
def engine_mount_point
|
118
123
|
path_parts = request_path.split('/')
|
119
124
|
if path_parts.length >= 3
|
@@ -134,13 +139,24 @@ module SolidQueueMonitor
|
|
134
139
|
params.empty? ? '' : "&#{params.join('&')}"
|
135
140
|
end
|
136
141
|
|
137
|
-
# Helper method to get the full path for a route
|
138
142
|
def full_path(route_name, *args)
|
139
|
-
# Try to use the engine routes first
|
140
143
|
SolidQueueMonitor::Engine.routes.url_helpers.send(route_name, *args)
|
141
144
|
rescue NoMethodError
|
142
|
-
# Fall back to main app routes
|
143
145
|
Rails.application.routes.url_helpers.send("solid_queue_#{route_name}", *args)
|
144
146
|
end
|
147
|
+
|
148
|
+
def format_job_arguments(job_data)
|
149
|
+
args = if job_data['arguments'].is_a?(Array)
|
150
|
+
if job_data['arguments'].first.is_a?(Hash) && job_data['arguments'].first['_aj_ruby2_keywords'].present?
|
151
|
+
job_data['arguments'].first.except('_aj_ruby2_keywords')
|
152
|
+
else
|
153
|
+
job_data['arguments']
|
154
|
+
end
|
155
|
+
else
|
156
|
+
job_data['arguments']
|
157
|
+
end
|
158
|
+
|
159
|
+
args.inspect
|
160
|
+
end
|
145
161
|
end
|
146
162
|
end
|
@@ -46,13 +46,14 @@ module SolidQueueMonitor
|
|
46
46
|
|
47
47
|
<div class="bulk-actions-bar">
|
48
48
|
<button type="button" class="action-button execute-button" id="execute-selected-top" disabled>Execute Selected</button>
|
49
|
+
<button type="button" class="action-button discard-button" id="reject-selected-top" disabled>Reject Selected</button>
|
49
50
|
</div>
|
50
51
|
HTML
|
51
52
|
end
|
52
53
|
|
53
54
|
def generate_table_with_actions
|
54
55
|
<<-HTML
|
55
|
-
<form id="scheduled-jobs-form"
|
56
|
+
<form id="scheduled-jobs-form" method="POST">
|
56
57
|
#{generate_table}
|
57
58
|
</form>
|
58
59
|
<script>
|
@@ -60,17 +61,18 @@ module SolidQueueMonitor
|
|
60
61
|
const selectAllCheckbox = document.querySelector('th input[type="checkbox"]');
|
61
62
|
const jobCheckboxes = document.getElementsByName('job_ids[]');
|
62
63
|
const executeButton = document.getElementById('execute-selected-top');
|
64
|
+
const rejectButton = document.getElementById('reject-selected-top');
|
63
65
|
const form = document.getElementById('scheduled-jobs-form');
|
64
66
|
#{' '}
|
65
67
|
selectAllCheckbox.addEventListener('change', function() {
|
66
68
|
jobCheckboxes.forEach(checkbox => checkbox.checked = this.checked);
|
67
|
-
|
69
|
+
updateButtonStates();
|
68
70
|
});
|
69
71
|
|
70
72
|
jobCheckboxes.forEach(checkbox => {
|
71
73
|
checkbox.addEventListener('change', function() {
|
72
74
|
selectAllCheckbox.checked = Array.from(jobCheckboxes).every(cb => cb.checked);
|
73
|
-
|
75
|
+
updateButtonStates();
|
74
76
|
});
|
75
77
|
});
|
76
78
|
#{' '}
|
@@ -79,6 +81,31 @@ module SolidQueueMonitor
|
|
79
81
|
const selectedIds = Array.from(document.querySelectorAll('input[name="job_ids[]"]:checked')).map(cb => cb.value);
|
80
82
|
if (selectedIds.length === 0) return;
|
81
83
|
#{' '}
|
84
|
+
submitForm('#{execute_jobs_path}', selectedIds);
|
85
|
+
});
|
86
|
+
#{' '}
|
87
|
+
// Add event listener for the reject button
|
88
|
+
rejectButton.addEventListener('click', function() {
|
89
|
+
const selectedIds = Array.from(document.querySelectorAll('input[name="job_ids[]"]:checked')).map(cb => cb.value);
|
90
|
+
if (selectedIds.length === 0) return;
|
91
|
+
#{' '}
|
92
|
+
if (confirm('Are you sure you want to reject the selected jobs? This action cannot be undone.')) {
|
93
|
+
submitForm('#{reject_jobs_path}', selectedIds);
|
94
|
+
}
|
95
|
+
});
|
96
|
+
#{' '}
|
97
|
+
function submitForm(actionUrl, selectedIds) {
|
98
|
+
// Uncheck all checkboxes to prevent duplicate submission
|
99
|
+
document.querySelectorAll('input[name="job_ids[]"]').forEach(checkbox => {
|
100
|
+
checkbox.checked = false;
|
101
|
+
});
|
102
|
+
|
103
|
+
// Clear any existing hidden inputs
|
104
|
+
document.querySelectorAll('input[type="hidden"][name="job_ids[]"]').forEach(input => input.remove());
|
105
|
+
|
106
|
+
// Set form action
|
107
|
+
form.action = actionUrl;
|
108
|
+
|
82
109
|
// Add selected IDs as hidden inputs
|
83
110
|
selectedIds.forEach(id => {
|
84
111
|
const input = document.createElement('input');
|
@@ -87,18 +114,20 @@ module SolidQueueMonitor
|
|
87
114
|
input.value = id;
|
88
115
|
form.appendChild(input);
|
89
116
|
});
|
90
|
-
|
117
|
+
|
118
|
+
// Submit the form
|
91
119
|
form.submit();
|
92
|
-
}
|
120
|
+
}
|
93
121
|
#{' '}
|
94
|
-
function
|
122
|
+
function updateButtonStates() {
|
95
123
|
const checkboxes = document.getElementsByName('job_ids[]');
|
96
124
|
const checked = Array.from(checkboxes).some(cb => cb.checked);
|
97
125
|
executeButton.disabled = !checked;
|
126
|
+
rejectButton.disabled = !checked;
|
98
127
|
}
|
99
128
|
#{' '}
|
100
|
-
// Initialize button
|
101
|
-
|
129
|
+
// Initialize button states
|
130
|
+
updateButtonStates();
|
102
131
|
});
|
103
132
|
</script>
|
104
133
|
HTML
|
@@ -130,7 +159,7 @@ module SolidQueueMonitor
|
|
130
159
|
<<-HTML
|
131
160
|
<tr>
|
132
161
|
<td>
|
133
|
-
<input type="checkbox" name="job_ids[]" value="#{execution.id}"
|
162
|
+
<input type="checkbox" name="job_ids[]" value="#{execution.id}">
|
134
163
|
</td>
|
135
164
|
<td>#{execution.job.class_name}</td>
|
136
165
|
<td>#{execution.queue_name}</td>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidQueueMonitor
|
4
|
+
class RejectJobService
|
5
|
+
def call(id)
|
6
|
+
execution = SolidQueue::ScheduledExecution.find(id)
|
7
|
+
reject_job(execution)
|
8
|
+
end
|
9
|
+
|
10
|
+
def reject_many(ids)
|
11
|
+
return { success: false, message: 'No jobs selected' } if ids.blank?
|
12
|
+
|
13
|
+
success_count = 0
|
14
|
+
failed_count = 0
|
15
|
+
|
16
|
+
ids.each do |id|
|
17
|
+
execution = SolidQueue::ScheduledExecution.find_by(id: id)
|
18
|
+
if execution
|
19
|
+
reject_job(execution)
|
20
|
+
success_count += 1
|
21
|
+
else
|
22
|
+
failed_count += 1
|
23
|
+
end
|
24
|
+
rescue StandardError
|
25
|
+
failed_count += 1
|
26
|
+
end
|
27
|
+
|
28
|
+
if success_count.positive? && failed_count.zero?
|
29
|
+
{ success: true, message: 'All selected jobs have been rejected' }
|
30
|
+
elsif success_count.positive? && failed_count.positive?
|
31
|
+
{ success: true, message: "#{success_count} jobs rejected, #{failed_count} failed" }
|
32
|
+
else
|
33
|
+
{ success: false, message: 'Failed to reject jobs' }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def reject_job(execution)
|
40
|
+
ActiveRecord::Base.transaction do
|
41
|
+
# Mark the associated job as finished to indicate it was rejected
|
42
|
+
execution.job.update!(finished_at: Time.current)
|
43
|
+
|
44
|
+
# Remove the scheduled execution
|
45
|
+
execution.destroy
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -282,6 +282,51 @@ module SolidQueueMonitor
|
|
282
282
|
font-weight: 500;
|
283
283
|
}
|
284
284
|
|
285
|
+
/* Arguments styling */
|
286
|
+
.solid_queue_monitor .args-container {
|
287
|
+
position: relative;
|
288
|
+
max-height: 100px;
|
289
|
+
overflow: hidden;
|
290
|
+
}
|
291
|
+
|
292
|
+
.solid_queue_monitor .args-content {
|
293
|
+
display: block;
|
294
|
+
white-space: pre-wrap;
|
295
|
+
word-break: break-word;
|
296
|
+
max-height: 100px;
|
297
|
+
overflow-y: auto;
|
298
|
+
padding: 8px;
|
299
|
+
background: #f5f5f5;
|
300
|
+
border-radius: 4px;
|
301
|
+
font-size: 0.9em;
|
302
|
+
}
|
303
|
+
|
304
|
+
.solid_queue_monitor .args-single-line {
|
305
|
+
display: inline-block;
|
306
|
+
padding: 4px 8px;
|
307
|
+
background: #f5f5f5;
|
308
|
+
border-radius: 4px;
|
309
|
+
font-size: 0.9em;
|
310
|
+
}
|
311
|
+
|
312
|
+
.solid_queue_monitor .args-content::-webkit-scrollbar {
|
313
|
+
width: 8px;
|
314
|
+
}
|
315
|
+
|
316
|
+
.solid_queue_monitor .args-content::-webkit-scrollbar-track {
|
317
|
+
background: #f1f1f1;
|
318
|
+
border-radius: 4px;
|
319
|
+
}
|
320
|
+
|
321
|
+
.solid_queue_monitor .args-content::-webkit-scrollbar-thumb {
|
322
|
+
background: #888;
|
323
|
+
border-radius: 4px;
|
324
|
+
}
|
325
|
+
|
326
|
+
.solid_queue_monitor .args-content::-webkit-scrollbar-thumb:hover {
|
327
|
+
background: #666;
|
328
|
+
}
|
329
|
+
|
285
330
|
@media (max-width: 768px) {
|
286
331
|
.solid_queue_monitor .container {
|
287
332
|
padding: 0.5rem;
|
@@ -432,7 +477,6 @@ module SolidQueueMonitor
|
|
432
477
|
background: #e5e7eb;
|
433
478
|
}
|
434
479
|
|
435
|
-
/* Action buttons for retry/discard */
|
436
480
|
.solid_queue_monitor .action-button {
|
437
481
|
padding: 0.5rem 1rem;
|
438
482
|
border-radius: 0.375rem;
|
data/config/routes.rb
CHANGED
@@ -11,6 +11,7 @@ SolidQueueMonitor::Engine.routes.draw do
|
|
11
11
|
resources :queues, only: [:index]
|
12
12
|
|
13
13
|
post 'execute_jobs', to: 'scheduled_jobs#create', as: :execute_jobs
|
14
|
+
post 'reject_jobs', to: 'scheduled_jobs#reject_all', as: :reject_jobs
|
14
15
|
|
15
16
|
post 'retry_failed_job/:id', to: 'failed_jobs#retry', as: :retry_failed_job
|
16
17
|
post 'discard_failed_job/:id', to: 'failed_jobs#discard', as: :discard_failed_job
|
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.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vishal Sadriya
|
@@ -45,6 +45,7 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
+
- LICENSE
|
48
49
|
- README.md
|
49
50
|
- Rakefile
|
50
51
|
- app/controllers/solid_queue_monitor/application_controller.rb
|
@@ -70,6 +71,7 @@ files:
|
|
70
71
|
- app/services/solid_queue_monitor/failed_job_service.rb
|
71
72
|
- app/services/solid_queue_monitor/html_generator.rb
|
72
73
|
- app/services/solid_queue_monitor/pagination_service.rb
|
74
|
+
- app/services/solid_queue_monitor/reject_job_service.rb
|
73
75
|
- app/services/solid_queue_monitor/stats_calculator.rb
|
74
76
|
- app/services/solid_queue_monitor/status_calculator.rb
|
75
77
|
- app/services/solid_queue_monitor/stylesheet_generator.rb
|