solid_queue_monitor 1.3.0 → 2.0.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 +20 -4
- data/app/assets/javascripts/solid_queue_monitor/application.js +393 -0
- data/app/{services/solid_queue_monitor/stylesheet_generator.rb → assets/stylesheets/solid_queue_monitor/application.css} +23 -12
- data/app/controllers/solid_queue_monitor/application_controller.rb +2 -2
- data/app/controllers/solid_queue_monitor/assets_controller.rb +52 -0
- data/app/controllers/solid_queue_monitor/base_controller.rb +0 -29
- data/app/controllers/solid_queue_monitor/failed_jobs_controller.rb +3 -7
- data/app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/jobs_controller.rb +3 -7
- data/app/controllers/solid_queue_monitor/overview_controller.rb +3 -12
- data/app/controllers/solid_queue_monitor/queues_controller.rb +4 -18
- data/app/controllers/solid_queue_monitor/ready_jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/recurring_jobs_controller.rb +3 -6
- data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +3 -7
- data/app/controllers/solid_queue_monitor/search_controller.rb +3 -4
- data/app/controllers/solid_queue_monitor/workers_controller.rb +24 -8
- data/app/helpers/solid_queue_monitor/application_helper.rb +46 -0
- data/app/helpers/solid_queue_monitor/chart_helper.rb +293 -0
- data/app/helpers/solid_queue_monitor/job_details_helper.rb +66 -0
- data/app/helpers/solid_queue_monitor/jobs_helper.rb +134 -0
- data/app/helpers/solid_queue_monitor/pagination_helper.rb +23 -0
- data/app/helpers/solid_queue_monitor/sort_helper.rb +30 -0
- data/app/helpers/solid_queue_monitor/workers_helper.rb +88 -0
- data/app/services/solid_queue_monitor/asset_cache.rb +56 -0
- data/app/views/layouts/solid_queue_monitor/application.html.erb +25 -0
- data/app/views/solid_queue_monitor/failed_jobs/_row.html.erb +26 -0
- data/app/views/solid_queue_monitor/failed_jobs/index.html.erb +38 -0
- data/app/views/solid_queue_monitor/in_progress_jobs/_row.html.erb +13 -0
- data/app/views/solid_queue_monitor/in_progress_jobs/index.html.erb +25 -0
- data/app/views/solid_queue_monitor/jobs/_arguments.html.erb +9 -0
- data/app/views/solid_queue_monitor/jobs/_error.html.erb +26 -0
- data/app/views/solid_queue_monitor/jobs/_execution_history.html.erb +25 -0
- data/app/views/solid_queue_monitor/jobs/_header.html.erb +37 -0
- data/app/views/solid_queue_monitor/jobs/_metadata.html.erb +22 -0
- data/app/views/solid_queue_monitor/jobs/_raw_data.html.erb +11 -0
- data/app/views/solid_queue_monitor/jobs/_timeline.html.erb +29 -0
- data/app/views/solid_queue_monitor/jobs/_timing.html.erb +9 -0
- data/app/views/solid_queue_monitor/jobs/_worker.html.erb +12 -0
- data/app/views/solid_queue_monitor/jobs/show.html.erb +22 -0
- data/app/views/solid_queue_monitor/overview/_chart.html.erb +1 -0
- data/app/views/solid_queue_monitor/overview/_recent_job_row.html.erb +26 -0
- data/app/views/solid_queue_monitor/overview/_recent_jobs.html.erb +31 -0
- data/app/views/solid_queue_monitor/overview/_stat_card.html.erb +4 -0
- data/app/views/solid_queue_monitor/overview/_stats.html.erb +11 -0
- data/app/views/solid_queue_monitor/overview/index.html.erb +9 -0
- data/app/views/solid_queue_monitor/queues/_job_row.html.erb +26 -0
- data/app/views/solid_queue_monitor/queues/_row.html.erb +33 -0
- data/app/views/solid_queue_monitor/queues/index.html.erb +18 -0
- data/app/views/solid_queue_monitor/queues/show.html.erb +63 -0
- data/app/views/solid_queue_monitor/ready_jobs/_row.html.erb +7 -0
- data/app/views/solid_queue_monitor/ready_jobs/index.html.erb +25 -0
- data/app/views/solid_queue_monitor/recurring_jobs/_row.html.erb +8 -0
- data/app/views/solid_queue_monitor/recurring_jobs/index.html.erb +26 -0
- data/app/views/solid_queue_monitor/scheduled_jobs/_row.html.erb +7 -0
- data/app/views/solid_queue_monitor/scheduled_jobs/index.html.erb +36 -0
- data/app/views/solid_queue_monitor/search/_completed_row.html.erb +6 -0
- data/app/views/solid_queue_monitor/search/_failed_row.html.erb +6 -0
- data/app/views/solid_queue_monitor/search/_job_row.html.erb +9 -0
- data/app/views/solid_queue_monitor/search/_recurring_row.html.erb +6 -0
- data/app/views/solid_queue_monitor/search/_section.html.erb +25 -0
- data/app/views/solid_queue_monitor/search/index.html.erb +23 -0
- data/app/views/solid_queue_monitor/shared/_filters.html.erb +48 -0
- data/app/views/solid_queue_monitor/shared/_flash.html.erb +17 -0
- data/app/views/solid_queue_monitor/shared/_footer.html.erb +3 -0
- data/app/views/solid_queue_monitor/shared/_header.html.erb +81 -0
- data/app/views/solid_queue_monitor/shared/_jobs_table.html.erb +20 -0
- data/app/views/solid_queue_monitor/shared/_pagination.html.erb +25 -0
- data/app/views/solid_queue_monitor/workers/_row.html.erb +22 -0
- data/app/views/solid_queue_monitor/workers/index.html.erb +82 -0
- data/config/routes.rb +6 -1
- data/lib/solid_queue_monitor/engine.rb +2 -0
- data/lib/solid_queue_monitor/version.rb +1 -1
- metadata +57 -17
- data/app/presenters/solid_queue_monitor/base_presenter.rb +0 -211
- data/app/presenters/solid_queue_monitor/failed_jobs_presenter.rb +0 -225
- data/app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb +0 -84
- data/app/presenters/solid_queue_monitor/job_details_presenter.rb +0 -707
- data/app/presenters/solid_queue_monitor/jobs_presenter.rb +0 -144
- data/app/presenters/solid_queue_monitor/queue_details_presenter.rb +0 -195
- data/app/presenters/solid_queue_monitor/queues_presenter.rb +0 -89
- data/app/presenters/solid_queue_monitor/ready_jobs_presenter.rb +0 -81
- data/app/presenters/solid_queue_monitor/recurring_jobs_presenter.rb +0 -81
- data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +0 -178
- data/app/presenters/solid_queue_monitor/search_results_presenter.rb +0 -190
- data/app/presenters/solid_queue_monitor/stats_presenter.rb +0 -36
- data/app/presenters/solid_queue_monitor/workers_presenter.rb +0 -325
- data/app/services/solid_queue_monitor/chart_presenter.rb +0 -239
- data/app/services/solid_queue_monitor/html_generator.rb +0 -427
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SolidQueueMonitor
|
|
4
|
-
class FailedJobsPresenter < BasePresenter
|
|
5
|
-
include Rails.application.routes.url_helpers
|
|
6
|
-
include SolidQueueMonitor::Engine.routes.url_helpers
|
|
7
|
-
|
|
8
|
-
def initialize(jobs, current_page: 1, total_pages: 1, filters: {}, sort: {}, nonce: nil)
|
|
9
|
-
@jobs = jobs
|
|
10
|
-
@current_page = current_page
|
|
11
|
-
@total_pages = total_pages
|
|
12
|
-
@filters = filters
|
|
13
|
-
@sort = sort
|
|
14
|
-
@nonce = nonce
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def render
|
|
18
|
-
section_wrapper('Failed Jobs',
|
|
19
|
-
generate_filter_form + generate_table + generate_pagination(@current_page, @total_pages))
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def script_tag_open
|
|
25
|
-
@nonce ? %(<script nonce="#{@nonce}">) : '<script>'
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def generate_filter_form
|
|
29
|
-
<<-HTML
|
|
30
|
-
<div class="filter-form-container">
|
|
31
|
-
<form method="get" action="#{failed_jobs_path}" class="filter-form">
|
|
32
|
-
<div class="filter-group">
|
|
33
|
-
<label for="class_name">Job Class:</label>
|
|
34
|
-
<input type="text" name="class_name" id="class_name" value="#{@filters[:class_name]}" placeholder="Filter by class name">
|
|
35
|
-
</div>
|
|
36
|
-
|
|
37
|
-
<div class="filter-group">
|
|
38
|
-
<label for="queue_name">Queue:</label>
|
|
39
|
-
<input type="text" name="queue_name" id="queue_name" value="#{@filters[:queue_name]}" placeholder="Filter by queue">
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<div class="filter-group">
|
|
43
|
-
<label for="arguments">Arguments:</label>
|
|
44
|
-
<input type="text" name="arguments" id="arguments" value="#{@filters[:arguments]}" placeholder="Filter by arguments">
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div class="filter-actions">
|
|
48
|
-
<button type="submit" class="filter-button">Apply Filters</button>
|
|
49
|
-
<a href="#{failed_jobs_path}" class="reset-button">Reset</a>
|
|
50
|
-
</div>
|
|
51
|
-
</form>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div class="bulk-actions-bar">
|
|
55
|
-
<button type="button" class="action-button retry-button" id="retry-selected-top" disabled>Retry Selected</button>
|
|
56
|
-
<button type="button" class="action-button discard-button" id="discard-selected-top" disabled>Discard Selected</button>
|
|
57
|
-
</div>
|
|
58
|
-
HTML
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def generate_table
|
|
62
|
-
<<-HTML
|
|
63
|
-
<form method="post" id="failed-jobs-form">
|
|
64
|
-
<div class="table-container">
|
|
65
|
-
<table>
|
|
66
|
-
<thead>
|
|
67
|
-
<tr>
|
|
68
|
-
<th><input type="checkbox" id="select-all" class="select-all-checkbox"></th>
|
|
69
|
-
#{sortable_header('class_name', 'Job')}
|
|
70
|
-
#{sortable_header('queue_name', 'Queue')}
|
|
71
|
-
<th>Error</th>
|
|
72
|
-
<th>Arguments</th>
|
|
73
|
-
#{sortable_header('created_at', 'Failed At')}
|
|
74
|
-
<th>Actions</th>
|
|
75
|
-
</tr>
|
|
76
|
-
</thead>
|
|
77
|
-
<tbody>
|
|
78
|
-
#{@jobs.map { |failed_execution| generate_row(failed_execution) }.join}
|
|
79
|
-
</tbody>
|
|
80
|
-
</table>
|
|
81
|
-
</div>
|
|
82
|
-
</form>
|
|
83
|
-
|
|
84
|
-
#{script_tag_open}
|
|
85
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
86
|
-
var selectAllHeader = document.getElementById('select-all');
|
|
87
|
-
var checkboxes = document.querySelectorAll('.job-checkbox');
|
|
88
|
-
var retrySelectedBtn = document.getElementById('retry-selected-top');
|
|
89
|
-
var discardSelectedBtn = document.getElementById('discard-selected-top');
|
|
90
|
-
var form = document.getElementById('failed-jobs-form');
|
|
91
|
-
|
|
92
|
-
function updateButtonState() {
|
|
93
|
-
var checkedBoxes = document.querySelectorAll('.job-checkbox:checked');
|
|
94
|
-
retrySelectedBtn.disabled = checkedBoxes.length === 0;
|
|
95
|
-
discardSelectedBtn.disabled = checkedBoxes.length === 0;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
selectAllHeader.addEventListener('change', function() {
|
|
99
|
-
checkboxes.forEach(function(cb) { cb.checked = selectAllHeader.checked; });
|
|
100
|
-
updateButtonState();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
checkboxes.forEach(function(cb) {
|
|
104
|
-
cb.addEventListener('change', function() {
|
|
105
|
-
updateButtonState();
|
|
106
|
-
var allChecked = document.querySelectorAll('.job-checkbox:checked').length === checkboxes.length;
|
|
107
|
-
selectAllHeader.checked = allChecked;
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
function bulkSubmit(action, promptMsg) {
|
|
112
|
-
var ids = Array.from(document.querySelectorAll('.job-checkbox:checked')).map(function(cb) { return cb.value; });
|
|
113
|
-
if (ids.length === 0) return;
|
|
114
|
-
if (!confirm(promptMsg)) return;
|
|
115
|
-
form.action = action;
|
|
116
|
-
appendHidden(form, 'redirect_cleanly', 'true');
|
|
117
|
-
ids.forEach(function(id) { appendHidden(form, 'job_ids[]', id); });
|
|
118
|
-
form.submit();
|
|
119
|
-
setTimeout(function() { window.history.replaceState({}, '', window.location.pathname); }, 100);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function appendHidden(f, name, value) {
|
|
123
|
-
var input = document.createElement('input');
|
|
124
|
-
input.type = 'hidden';
|
|
125
|
-
input.name = name;
|
|
126
|
-
input.value = value;
|
|
127
|
-
f.appendChild(input);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
retrySelectedBtn.addEventListener('click', function() {
|
|
131
|
-
bulkSubmit('#{retry_failed_jobs_path}', 'Are you sure you want to retry the selected jobs?');
|
|
132
|
-
});
|
|
133
|
-
discardSelectedBtn.addEventListener('click', function() {
|
|
134
|
-
bulkSubmit('#{discard_failed_jobs_path}', 'Are you sure you want to discard the selected jobs?');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
function submitRowAction(action, id) {
|
|
138
|
-
var f = document.createElement('form');
|
|
139
|
-
f.method = 'post';
|
|
140
|
-
f.action = action.replace('PLACEHOLDER', id);
|
|
141
|
-
appendHidden(f, 'redirect_cleanly', 'true');
|
|
142
|
-
document.body.appendChild(f);
|
|
143
|
-
f.submit();
|
|
144
|
-
setTimeout(function() { window.history.replaceState({}, '', window.location.pathname); }, 100);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
document.addEventListener('click', function(e) {
|
|
148
|
-
var btn = e.target.closest('[data-action]');
|
|
149
|
-
if (!btn) return;
|
|
150
|
-
var id = btn.dataset.jobId;
|
|
151
|
-
if (btn.dataset.action === 'retry-failed-job') {
|
|
152
|
-
submitRowAction('#{retry_failed_job_path(id: 'PLACEHOLDER')}', id);
|
|
153
|
-
} else if (btn.dataset.action === 'discard-failed-job') {
|
|
154
|
-
if (confirm('Are you sure you want to discard this job?')) {
|
|
155
|
-
submitRowAction('#{discard_failed_job_path(id: 'PLACEHOLDER')}', id);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
updateButtonState();
|
|
161
|
-
});
|
|
162
|
-
</script>
|
|
163
|
-
HTML
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def generate_row(failed_execution)
|
|
167
|
-
job = failed_execution.job
|
|
168
|
-
error = parse_error(failed_execution.error)
|
|
169
|
-
|
|
170
|
-
<<-HTML
|
|
171
|
-
<tr>
|
|
172
|
-
<td><input type="checkbox" class="job-checkbox" value="#{failed_execution.id}"></td>
|
|
173
|
-
<td>
|
|
174
|
-
<div class="job-class"><a href="#{job_path(job)}" class="job-class-link">#{job.class_name}</a></div>
|
|
175
|
-
<div class="job-meta">
|
|
176
|
-
<span class="job-timestamp">Queued at: #{format_datetime(job.created_at)}</span>
|
|
177
|
-
</div>
|
|
178
|
-
</td>
|
|
179
|
-
<td>
|
|
180
|
-
<div class="job-queue">#{queue_link(job.queue_name)}</div>
|
|
181
|
-
</td>
|
|
182
|
-
<td>
|
|
183
|
-
<div class="error-message">#{error[:message].to_s.truncate(100)}</div>
|
|
184
|
-
</td>
|
|
185
|
-
<td>#{format_arguments(job.arguments)}</td>
|
|
186
|
-
<td>#{format_datetime(failed_execution.created_at)}</td>
|
|
187
|
-
<td class="actions-cell">
|
|
188
|
-
<div class="job-actions">
|
|
189
|
-
<button type="button" data-action="retry-failed-job" data-job-id="#{failed_execution.id}" class="action-button retry-button">Retry</button>
|
|
190
|
-
<button type="button" data-action="discard-failed-job" data-job-id="#{failed_execution.id}" class="action-button discard-button">Discard</button>
|
|
191
|
-
</div>
|
|
192
|
-
</td>
|
|
193
|
-
</tr>
|
|
194
|
-
HTML
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def parse_error(error)
|
|
198
|
-
return { message: 'Unknown error', backtrace: '' } unless error
|
|
199
|
-
|
|
200
|
-
if error.is_a?(String)
|
|
201
|
-
{ message: error, backtrace: '' }
|
|
202
|
-
elsif error.is_a?(Hash)
|
|
203
|
-
message = error['message'] || error[:message] || 'Unknown error'
|
|
204
|
-
backtrace = error['backtrace'] || error[:backtrace] || []
|
|
205
|
-
backtrace = backtrace.join("\n") if backtrace.is_a?(Array)
|
|
206
|
-
{ message: message, backtrace: backtrace }
|
|
207
|
-
else
|
|
208
|
-
{ message: 'Unknown error format', backtrace: error.to_s }
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
def get_queue_name(failed_execution, job)
|
|
213
|
-
# Try to get queue_name from failed_execution if the method exists
|
|
214
|
-
if failed_execution.respond_to?(:queue_name) && !failed_execution.queue_name.nil?
|
|
215
|
-
failed_execution.queue_name
|
|
216
|
-
else
|
|
217
|
-
# Fall back to job's queue_name
|
|
218
|
-
job.queue_name
|
|
219
|
-
end
|
|
220
|
-
rescue NoMethodError
|
|
221
|
-
# If there's an error accessing queue_name, fall back to job's queue_name
|
|
222
|
-
job.queue_name
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
end
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SolidQueueMonitor
|
|
4
|
-
class InProgressJobsPresenter < BasePresenter
|
|
5
|
-
include SolidQueueMonitor::Engine.routes.url_helpers
|
|
6
|
-
|
|
7
|
-
def initialize(jobs, current_page: 1, total_pages: 1, filters: {}, sort: {})
|
|
8
|
-
@jobs = jobs
|
|
9
|
-
@current_page = current_page
|
|
10
|
-
@total_pages = total_pages
|
|
11
|
-
@filters = filters
|
|
12
|
-
@sort = sort
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def render
|
|
16
|
-
section_wrapper('In Progress Jobs',
|
|
17
|
-
generate_filter_form + generate_table + generate_pagination(@current_page, @total_pages))
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def generate_filter_form
|
|
23
|
-
<<-HTML
|
|
24
|
-
<div class="filter-form-container">
|
|
25
|
-
<form method="get" action="#{in_progress_jobs_path}" class="filter-form">
|
|
26
|
-
<div class="filter-group">
|
|
27
|
-
<label for="class_name">Job Class:</label>
|
|
28
|
-
<input type="text" name="class_name" id="class_name" value="#{@filters[:class_name]}" placeholder="Filter by class name">
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<div class="filter-group">
|
|
32
|
-
<label for="arguments">Arguments:</label>
|
|
33
|
-
<input type="text" name="arguments" id="arguments" value="#{@filters[:arguments]}" placeholder="Filter by arguments">
|
|
34
|
-
</div>
|
|
35
|
-
|
|
36
|
-
<div class="filter-actions">
|
|
37
|
-
<button type="submit" class="filter-button">Apply Filters</button>
|
|
38
|
-
<a href="#{in_progress_jobs_path}" class="reset-button">Reset</a>
|
|
39
|
-
</div>
|
|
40
|
-
</form>
|
|
41
|
-
</div>
|
|
42
|
-
HTML
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def generate_table
|
|
46
|
-
<<-HTML
|
|
47
|
-
<div class="table-container">
|
|
48
|
-
<table>
|
|
49
|
-
<thead>
|
|
50
|
-
<tr>
|
|
51
|
-
#{sortable_header('class_name', 'Job')}
|
|
52
|
-
#{sortable_header('queue_name', 'Queue')}
|
|
53
|
-
<th>Arguments</th>
|
|
54
|
-
#{sortable_header('created_at', 'Started At')}
|
|
55
|
-
<th>Process ID</th>
|
|
56
|
-
</tr>
|
|
57
|
-
</thead>
|
|
58
|
-
<tbody>
|
|
59
|
-
#{@jobs.map { |execution| generate_row(execution) }.join}
|
|
60
|
-
</tbody>
|
|
61
|
-
</table>
|
|
62
|
-
</div>
|
|
63
|
-
HTML
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def generate_row(execution)
|
|
67
|
-
job = execution.job
|
|
68
|
-
<<-HTML
|
|
69
|
-
<tr>
|
|
70
|
-
<td>
|
|
71
|
-
<div class="job-class"><a href="#{job_path(job)}" class="job-class-link">#{job.class_name}</a></div>
|
|
72
|
-
<div class="job-meta">
|
|
73
|
-
<span class="job-timestamp">Queued at: #{format_datetime(job.created_at)}</span>
|
|
74
|
-
</div>
|
|
75
|
-
</td>
|
|
76
|
-
<td>#{queue_link(job.queue_name)}</td>
|
|
77
|
-
<td>#{format_arguments(job.arguments)}</td>
|
|
78
|
-
<td>#{format_datetime(execution.created_at)}</td>
|
|
79
|
-
<td>#{execution.process_id}</td>
|
|
80
|
-
</tr>
|
|
81
|
-
HTML
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|