solid_queue_monitor 1.0.1 → 1.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 +16 -1
- data/app/controllers/solid_queue_monitor/base_controller.rb +49 -33
- data/app/controllers/solid_queue_monitor/failed_jobs_controller.rb +7 -3
- data/app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb +9 -7
- data/app/controllers/solid_queue_monitor/overview_controller.rb +13 -9
- data/app/controllers/solid_queue_monitor/queues_controller.rb +39 -16
- data/app/controllers/solid_queue_monitor/ready_jobs_controller.rb +7 -3
- data/app/controllers/solid_queue_monitor/recurring_jobs_controller.rb +7 -3
- data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +7 -3
- data/app/controllers/solid_queue_monitor/search_controller.rb +12 -0
- data/app/controllers/solid_queue_monitor/workers_controller.rb +7 -4
- data/app/presenters/solid_queue_monitor/base_presenter.rb +47 -5
- data/app/presenters/solid_queue_monitor/failed_jobs_presenter.rb +6 -6
- data/app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb +5 -4
- data/app/presenters/solid_queue_monitor/jobs_presenter.rb +5 -4
- data/app/presenters/solid_queue_monitor/queue_details_presenter.rb +4 -3
- data/app/presenters/solid_queue_monitor/queues_presenter.rb +10 -22
- data/app/presenters/solid_queue_monitor/ready_jobs_presenter.rb +6 -5
- data/app/presenters/solid_queue_monitor/recurring_jobs_presenter.rb +6 -5
- data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +5 -4
- data/app/presenters/solid_queue_monitor/search_results_presenter.rb +190 -0
- data/app/presenters/solid_queue_monitor/stats_presenter.rb +1 -2
- data/app/presenters/solid_queue_monitor/workers_presenter.rb +4 -3
- data/app/services/solid_queue_monitor/chart_data_service.rb +53 -57
- data/app/services/solid_queue_monitor/html_generator.rb +23 -2
- data/app/services/solid_queue_monitor/search_service.rb +126 -0
- data/app/services/solid_queue_monitor/stats_calculator.rb +12 -8
- data/app/services/solid_queue_monitor/stylesheet_generator.rb +118 -0
- data/config/routes.rb +1 -0
- data/lib/generators/solid_queue_monitor/templates/initializer.rb +3 -0
- data/lib/solid_queue_monitor/version.rb +1 -1
- data/lib/solid_queue_monitor.rb +2 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc739f10c21ca234c61c65a5b94d9c548cfc8d332dac0d1db3311d0d113fe035
|
|
4
|
+
data.tar.gz: 4a131b01c6c3e330b46413029278dbcee09e4f6410f1edf44b14da5902ec5b1a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02b79913620ac69eb63c15b2c92a1ba76f44f981036f977eb72fe684faa28ae2057ea187582e8bbd5cb09c0de9f030be89e07b8e8e8e754ee0ffebd6e7c33f84
|
|
7
|
+
data.tar.gz: 853dc16edd433afa568a7cef1efb78b295d54595a72e1e7c177e7991ff50b3e396d80ab1092754e65972dbe0fa9072e2a4563ab68b4785ebe7b5d77b5373e086
|
data/README.md
CHANGED
|
@@ -35,6 +35,8 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
|
|
|
35
35
|
- **Failed Jobs**: Track and debug failed jobs, with the ability to retry or discard them
|
|
36
36
|
- **Queue Management**: View and filter jobs by queue with pause/resume controls
|
|
37
37
|
- **Pause/Resume Queues**: Temporarily stop processing jobs on specific queues for incident response
|
|
38
|
+
- **Global Search**: Search across all job types by class name, queue, arguments, job ID, and error messages
|
|
39
|
+
- **Sortable Columns**: Click column headers to sort job tables by any column with ascending/descending toggle
|
|
38
40
|
- **Advanced Job Filtering**: Filter jobs by class name, queue, status, and job arguments
|
|
39
41
|
- **Quick Actions**: Retry or discard failed jobs, execute or reject scheduled jobs directly from any view
|
|
40
42
|
- **Performance Optimized**: Designed for high-volume applications with smart pagination
|
|
@@ -70,7 +72,7 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
|
|
|
70
72
|
Add this line to your application's Gemfile:
|
|
71
73
|
|
|
72
74
|
```ruby
|
|
73
|
-
gem 'solid_queue_monitor', '~> 1.
|
|
75
|
+
gem 'solid_queue_monitor', '~> 1.2'
|
|
74
76
|
```
|
|
75
77
|
|
|
76
78
|
Then execute:
|
|
@@ -116,9 +118,22 @@ SolidQueueMonitor.setup do |config|
|
|
|
116
118
|
|
|
117
119
|
# Auto-refresh interval in seconds (default: 30)
|
|
118
120
|
config.auto_refresh_interval = 30
|
|
121
|
+
|
|
122
|
+
# Disable the chart on the overview page to skip chart queries entirely
|
|
123
|
+
# config.show_chart = true
|
|
119
124
|
end
|
|
120
125
|
```
|
|
121
126
|
|
|
127
|
+
### Performance at Scale
|
|
128
|
+
|
|
129
|
+
SolidQueueMonitor is optimized for large datasets (millions of rows in `solid_queue_jobs`). All dashboard queries are designed to stay fast regardless of table size.
|
|
130
|
+
|
|
131
|
+
If you don't need the job activity chart, disable it to skip chart queries entirely:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
config.show_chart = false
|
|
135
|
+
```
|
|
136
|
+
|
|
122
137
|
### Authentication
|
|
123
138
|
|
|
124
139
|
By default, Solid Queue Monitor does not require authentication to access the dashboard. This makes it easy to get started in development environments.
|
|
@@ -6,7 +6,7 @@ module SolidQueueMonitor
|
|
|
6
6
|
PaginationService.new(relation, current_page, per_page).paginate
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def render_page(title, content)
|
|
9
|
+
def render_page(title, content, search_query: nil)
|
|
10
10
|
# Get flash message from instance variable (set by set_flash_message) or session
|
|
11
11
|
message = @flash_message
|
|
12
12
|
message_type = @flash_type
|
|
@@ -27,7 +27,8 @@ module SolidQueueMonitor
|
|
|
27
27
|
title: title,
|
|
28
28
|
content: content,
|
|
29
29
|
message: message,
|
|
30
|
-
message_type: message_type
|
|
30
|
+
message_type: message_type,
|
|
31
|
+
search_query: search_query
|
|
31
32
|
).generate
|
|
32
33
|
|
|
33
34
|
render html: html.html_safe
|
|
@@ -90,17 +91,13 @@ module SolidQueueMonitor
|
|
|
90
91
|
when 'completed'
|
|
91
92
|
relation = relation.where.not(finished_at: nil)
|
|
92
93
|
when 'failed'
|
|
93
|
-
|
|
94
|
-
relation = relation.where(id: failed_job_ids)
|
|
94
|
+
relation = relation.where(id: SolidQueue::FailedExecution.select(:job_id))
|
|
95
95
|
when 'scheduled'
|
|
96
|
-
|
|
97
|
-
relation = relation.where(id: scheduled_job_ids)
|
|
96
|
+
relation = relation.where(id: SolidQueue::ScheduledExecution.select(:job_id))
|
|
98
97
|
when 'pending'
|
|
99
|
-
# Pending jobs are those that are not completed, failed, or scheduled
|
|
100
|
-
failed_job_ids = SolidQueue::FailedExecution.pluck(:job_id)
|
|
101
|
-
scheduled_job_ids = SolidQueue::ScheduledExecution.pluck(:job_id)
|
|
102
98
|
relation = relation.where(finished_at: nil)
|
|
103
|
-
.where.not(id:
|
|
99
|
+
.where.not(id: SolidQueue::FailedExecution.select(:job_id))
|
|
100
|
+
.where.not(id: SolidQueue::ScheduledExecution.select(:job_id))
|
|
104
101
|
end
|
|
105
102
|
end
|
|
106
103
|
|
|
@@ -116,16 +113,13 @@ module SolidQueueMonitor
|
|
|
116
113
|
return relation unless params[:class_name].present? || params[:queue_name].present? || params[:arguments].present?
|
|
117
114
|
|
|
118
115
|
if params[:class_name].present?
|
|
119
|
-
|
|
120
|
-
relation = relation.where(job_id: job_ids)
|
|
116
|
+
relation = relation.where(job_id: SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").select(:id))
|
|
121
117
|
end
|
|
122
118
|
|
|
123
119
|
relation = relation.where('queue_name LIKE ?', "%#{params[:queue_name]}%") if params[:queue_name].present?
|
|
124
120
|
|
|
125
|
-
# Add arguments filtering
|
|
126
121
|
if params[:arguments].present?
|
|
127
|
-
|
|
128
|
-
relation = relation.where(job_id: job_ids)
|
|
122
|
+
relation = relation.where(job_id: SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").select(:id))
|
|
129
123
|
end
|
|
130
124
|
|
|
131
125
|
relation
|
|
@@ -135,16 +129,13 @@ module SolidQueueMonitor
|
|
|
135
129
|
return relation unless params[:class_name].present? || params[:queue_name].present? || params[:arguments].present?
|
|
136
130
|
|
|
137
131
|
if params[:class_name].present?
|
|
138
|
-
|
|
139
|
-
relation = relation.where(job_id: job_ids)
|
|
132
|
+
relation = relation.where(job_id: SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").select(:id))
|
|
140
133
|
end
|
|
141
134
|
|
|
142
135
|
relation = relation.where('queue_name LIKE ?', "%#{params[:queue_name]}%") if params[:queue_name].present?
|
|
143
136
|
|
|
144
|
-
# Add arguments filtering
|
|
145
137
|
if params[:arguments].present?
|
|
146
|
-
|
|
147
|
-
relation = relation.where(job_id: job_ids)
|
|
138
|
+
relation = relation.where(job_id: SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").select(:id))
|
|
148
139
|
end
|
|
149
140
|
|
|
150
141
|
relation
|
|
@@ -169,25 +160,19 @@ module SolidQueueMonitor
|
|
|
169
160
|
return relation unless params[:class_name].present? || params[:queue_name].present? || params[:arguments].present?
|
|
170
161
|
|
|
171
162
|
if params[:class_name].present?
|
|
172
|
-
|
|
173
|
-
relation = relation.where(job_id: job_ids)
|
|
163
|
+
relation = relation.where(job_id: SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").select(:id))
|
|
174
164
|
end
|
|
175
165
|
|
|
176
166
|
if params[:queue_name].present?
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
job_ids = SolidQueue::Job.where('queue_name LIKE ?', "%#{params[:queue_name]}%").pluck(:id)
|
|
183
|
-
relation = relation.where(job_id: job_ids)
|
|
184
|
-
end
|
|
167
|
+
relation = if relation.column_names.include?('queue_name')
|
|
168
|
+
relation.where('queue_name LIKE ?', "%#{params[:queue_name]}%")
|
|
169
|
+
else
|
|
170
|
+
relation.where(job_id: SolidQueue::Job.where('queue_name LIKE ?', "%#{params[:queue_name]}%").select(:id))
|
|
171
|
+
end
|
|
185
172
|
end
|
|
186
173
|
|
|
187
|
-
# Add arguments filtering
|
|
188
174
|
if params[:arguments].present?
|
|
189
|
-
|
|
190
|
-
relation = relation.where(job_id: job_ids)
|
|
175
|
+
relation = relation.where(job_id: SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").select(:id))
|
|
191
176
|
end
|
|
192
177
|
|
|
193
178
|
relation
|
|
@@ -201,5 +186,36 @@ module SolidQueueMonitor
|
|
|
201
186
|
status: params[:status]
|
|
202
187
|
}
|
|
203
188
|
end
|
|
189
|
+
|
|
190
|
+
def sort_params
|
|
191
|
+
{
|
|
192
|
+
sort_by: params[:sort_by],
|
|
193
|
+
sort_direction: params[:sort_direction]
|
|
194
|
+
}
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def apply_sorting(relation, allowed_columns, default_column, default_direction = :desc)
|
|
198
|
+
column = sort_params[:sort_by]
|
|
199
|
+
direction = sort_params[:sort_direction]
|
|
200
|
+
column = default_column unless allowed_columns.include?(column)
|
|
201
|
+
direction = %w[asc desc].include?(direction) ? direction.to_sym : default_direction
|
|
202
|
+
relation.order(column => direction)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def apply_execution_sorting(relation, allowed_columns, default_column, default_direction = :desc)
|
|
206
|
+
column = sort_params[:sort_by]
|
|
207
|
+
direction = sort_params[:sort_direction]
|
|
208
|
+
column = default_column unless allowed_columns.include?(column)
|
|
209
|
+
direction = %w[asc desc].include?(direction) ? direction.to_sym : default_direction
|
|
210
|
+
|
|
211
|
+
# Columns that exist on the jobs table, not on execution tables
|
|
212
|
+
job_table_columns = %w[class_name queue_name]
|
|
213
|
+
|
|
214
|
+
if job_table_columns.include?(column)
|
|
215
|
+
relation.joins(:job).order("solid_queue_jobs.#{column}" => direction)
|
|
216
|
+
else
|
|
217
|
+
relation.order(column => direction)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
204
220
|
end
|
|
205
221
|
end
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class FailedJobsController < BaseController
|
|
5
|
+
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze
|
|
6
|
+
|
|
5
7
|
def index
|
|
6
|
-
base_query = SolidQueue::FailedExecution.includes(:job)
|
|
7
|
-
|
|
8
|
+
base_query = SolidQueue::FailedExecution.includes(:job)
|
|
9
|
+
sorted_query = apply_execution_sorting(filter_failed_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
|
|
10
|
+
@failed_jobs = paginate(sorted_query)
|
|
8
11
|
|
|
9
12
|
render_page('Failed Jobs', SolidQueueMonitor::FailedJobsPresenter.new(@failed_jobs[:records],
|
|
10
13
|
current_page: @failed_jobs[:current_page],
|
|
11
14
|
total_pages: @failed_jobs[:total_pages],
|
|
12
|
-
filters: filter_params
|
|
15
|
+
filters: filter_params,
|
|
16
|
+
sort: sort_params).render)
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def retry
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class InProgressJobsController < BaseController
|
|
5
|
+
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze
|
|
6
|
+
|
|
5
7
|
def index
|
|
6
|
-
base_query = SolidQueue::ClaimedExecution.includes(:job)
|
|
7
|
-
|
|
8
|
+
base_query = SolidQueue::ClaimedExecution.includes(:job)
|
|
9
|
+
sorted_query = apply_execution_sorting(filter_in_progress_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
|
|
10
|
+
@in_progress_jobs = paginate(sorted_query)
|
|
8
11
|
|
|
9
12
|
render_page('In Progress Jobs', SolidQueueMonitor::InProgressJobsPresenter.new(@in_progress_jobs[:records],
|
|
10
13
|
current_page: @in_progress_jobs[:current_page],
|
|
11
14
|
total_pages: @in_progress_jobs[:total_pages],
|
|
12
|
-
filters: filter_params
|
|
15
|
+
filters: filter_params,
|
|
16
|
+
sort: sort_params).render)
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
private
|
|
@@ -18,13 +22,11 @@ module SolidQueueMonitor
|
|
|
18
22
|
return relation if params[:class_name].blank? && params[:arguments].blank?
|
|
19
23
|
|
|
20
24
|
if params[:class_name].present?
|
|
21
|
-
|
|
22
|
-
relation = relation.where(job_id: job_ids)
|
|
25
|
+
relation = relation.where(job_id: SolidQueue::Job.where('class_name LIKE ?', "%#{params[:class_name]}%").select(:id))
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
if params[:arguments].present?
|
|
26
|
-
|
|
27
|
-
relation = relation.where(job_id: job_ids)
|
|
29
|
+
relation = relation.where(job_id: SolidQueue::Job.where('arguments::text ILIKE ?', "%#{params[:arguments]}%").select(:id))
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
relation
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class OverviewController < BaseController
|
|
5
|
+
SORTABLE_COLUMNS = %w[class_name queue_name created_at].freeze
|
|
6
|
+
|
|
5
7
|
def index
|
|
6
8
|
@stats = SolidQueueMonitor::StatsCalculator.calculate
|
|
7
|
-
@chart_data = SolidQueueMonitor::ChartDataService.new(time_range: time_range_param).calculate
|
|
9
|
+
@chart_data = SolidQueueMonitor.show_chart ? SolidQueueMonitor::ChartDataService.new(time_range: time_range_param).calculate : nil
|
|
8
10
|
|
|
9
|
-
recent_jobs_query = SolidQueue::Job.
|
|
10
|
-
|
|
11
|
+
recent_jobs_query = SolidQueue::Job.limit(100)
|
|
12
|
+
sorted_query = apply_sorting(filter_jobs(recent_jobs_query), SORTABLE_COLUMNS, 'created_at', :desc)
|
|
13
|
+
@recent_jobs = paginate(sorted_query)
|
|
11
14
|
|
|
12
15
|
preload_job_statuses(@recent_jobs[:records])
|
|
13
16
|
|
|
@@ -26,12 +29,13 @@ module SolidQueueMonitor
|
|
|
26
29
|
end
|
|
27
30
|
|
|
28
31
|
def generate_overview_content
|
|
29
|
-
SolidQueueMonitor::StatsPresenter.new(@stats).render
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
html = SolidQueueMonitor::StatsPresenter.new(@stats).render
|
|
33
|
+
html += SolidQueueMonitor::ChartPresenter.new(@chart_data).render if @chart_data
|
|
34
|
+
html + SolidQueueMonitor::JobsPresenter.new(@recent_jobs[:records],
|
|
35
|
+
current_page: @recent_jobs[:current_page],
|
|
36
|
+
total_pages: @recent_jobs[:total_pages],
|
|
37
|
+
filters: filter_params,
|
|
38
|
+
sort: sort_params).render
|
|
35
39
|
end
|
|
36
40
|
end
|
|
37
41
|
end
|
|
@@ -2,13 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class QueuesController < BaseController
|
|
5
|
+
SORTABLE_COLUMNS = %w[queue_name job_count].freeze
|
|
6
|
+
QUEUE_DETAILS_SORTABLE_COLUMNS = %w[class_name created_at].freeze
|
|
7
|
+
|
|
5
8
|
def index
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
base_query = SolidQueue::Job.group(:queue_name)
|
|
10
|
+
.select('queue_name, COUNT(*) as job_count')
|
|
11
|
+
@queues = apply_queue_sorting(base_query)
|
|
9
12
|
@paused_queues = QueuePauseService.paused_queues
|
|
13
|
+
@queue_stats = aggregate_queue_stats
|
|
10
14
|
|
|
11
|
-
render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(
|
|
15
|
+
render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(
|
|
16
|
+
@queues, @paused_queues,
|
|
17
|
+
queue_stats: @queue_stats,
|
|
18
|
+
sort: sort_params
|
|
19
|
+
).render)
|
|
12
20
|
end
|
|
13
21
|
|
|
14
22
|
def show
|
|
@@ -16,9 +24,9 @@ module SolidQueueMonitor
|
|
|
16
24
|
@paused = QueuePauseService.paused_queues.include?(@queue_name)
|
|
17
25
|
|
|
18
26
|
# Get all jobs for this queue with filtering and pagination
|
|
19
|
-
base_query = SolidQueue::Job.where(queue_name: @queue_name)
|
|
20
|
-
|
|
21
|
-
@jobs = paginate(
|
|
27
|
+
base_query = SolidQueue::Job.where(queue_name: @queue_name)
|
|
28
|
+
sorted_query = apply_sorting(filter_queue_jobs(base_query), QUEUE_DETAILS_SORTABLE_COLUMNS, 'created_at', :desc)
|
|
29
|
+
@jobs = paginate(sorted_query)
|
|
22
30
|
preload_job_statuses(@jobs[:records])
|
|
23
31
|
|
|
24
32
|
@counts = calculate_queue_counts(@queue_name)
|
|
@@ -31,7 +39,8 @@ module SolidQueueMonitor
|
|
|
31
39
|
counts: @counts,
|
|
32
40
|
current_page: @jobs[:current_page],
|
|
33
41
|
total_pages: @jobs[:total_pages],
|
|
34
|
-
filters: queue_filter_params
|
|
42
|
+
filters: queue_filter_params,
|
|
43
|
+
sort: sort_params
|
|
35
44
|
).render)
|
|
36
45
|
end
|
|
37
46
|
|
|
@@ -53,6 +62,15 @@ module SolidQueueMonitor
|
|
|
53
62
|
|
|
54
63
|
private
|
|
55
64
|
|
|
65
|
+
def aggregate_queue_stats
|
|
66
|
+
{
|
|
67
|
+
ready: SolidQueue::ReadyExecution.group(:queue_name).count,
|
|
68
|
+
scheduled: SolidQueue::ScheduledExecution.group(:queue_name).count,
|
|
69
|
+
failed: SolidQueue::FailedExecution.joins(:job)
|
|
70
|
+
.group('solid_queue_jobs.queue_name').count
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
56
74
|
def calculate_queue_counts(queue_name)
|
|
57
75
|
{
|
|
58
76
|
total: SolidQueue::Job.where(queue_name: queue_name).count,
|
|
@@ -73,17 +91,13 @@ module SolidQueueMonitor
|
|
|
73
91
|
when 'completed'
|
|
74
92
|
relation = relation.where.not(finished_at: nil)
|
|
75
93
|
when 'failed'
|
|
76
|
-
|
|
77
|
-
relation = relation.where(id: failed_job_ids)
|
|
94
|
+
relation = relation.where(id: SolidQueue::FailedExecution.select(:job_id))
|
|
78
95
|
when 'scheduled'
|
|
79
|
-
|
|
80
|
-
relation = relation.where(id: scheduled_job_ids)
|
|
96
|
+
relation = relation.where(id: SolidQueue::ScheduledExecution.select(:job_id))
|
|
81
97
|
when 'pending'
|
|
82
|
-
|
|
83
|
-
relation = relation.where(id: ready_job_ids)
|
|
98
|
+
relation = relation.where(id: SolidQueue::ReadyExecution.select(:job_id))
|
|
84
99
|
when 'in_progress'
|
|
85
|
-
|
|
86
|
-
relation = relation.where(id: claimed_job_ids)
|
|
100
|
+
relation = relation.where(id: SolidQueue::ClaimedExecution.select(:job_id))
|
|
87
101
|
end
|
|
88
102
|
end
|
|
89
103
|
|
|
@@ -97,5 +111,14 @@ module SolidQueueMonitor
|
|
|
97
111
|
status: params[:status]
|
|
98
112
|
}
|
|
99
113
|
end
|
|
114
|
+
|
|
115
|
+
def apply_queue_sorting(relation)
|
|
116
|
+
column = sort_params[:sort_by]
|
|
117
|
+
direction = sort_params[:sort_direction]
|
|
118
|
+
column = 'job_count' unless SORTABLE_COLUMNS.include?(column)
|
|
119
|
+
direction = 'desc' unless %w[asc desc].include?(direction)
|
|
120
|
+
|
|
121
|
+
relation.order("#{column} #{direction}")
|
|
122
|
+
end
|
|
100
123
|
end
|
|
101
124
|
end
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class ReadyJobsController < BaseController
|
|
5
|
+
SORTABLE_COLUMNS = %w[class_name queue_name priority created_at].freeze
|
|
6
|
+
|
|
5
7
|
def index
|
|
6
|
-
base_query = SolidQueue::ReadyExecution.includes(:job)
|
|
7
|
-
|
|
8
|
+
base_query = SolidQueue::ReadyExecution.includes(:job)
|
|
9
|
+
sorted_query = apply_execution_sorting(filter_ready_jobs(base_query), SORTABLE_COLUMNS, 'created_at', :desc)
|
|
10
|
+
@ready_jobs = paginate(sorted_query)
|
|
8
11
|
|
|
9
12
|
render_page('Ready Jobs', SolidQueueMonitor::ReadyJobsPresenter.new(@ready_jobs[:records],
|
|
10
13
|
current_page: @ready_jobs[:current_page],
|
|
11
14
|
total_pages: @ready_jobs[:total_pages],
|
|
12
|
-
filters: filter_params
|
|
15
|
+
filters: filter_params,
|
|
16
|
+
sort: sort_params).render)
|
|
13
17
|
end
|
|
14
18
|
end
|
|
15
19
|
end
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class RecurringJobsController < BaseController
|
|
5
|
+
SORTABLE_COLUMNS = %w[key class_name queue_name priority].freeze
|
|
6
|
+
|
|
5
7
|
def index
|
|
6
|
-
base_query = filter_recurring_jobs(SolidQueue::RecurringTask.
|
|
7
|
-
|
|
8
|
+
base_query = filter_recurring_jobs(SolidQueue::RecurringTask.all)
|
|
9
|
+
sorted_query = apply_sorting(base_query, SORTABLE_COLUMNS, 'key', :asc)
|
|
10
|
+
@recurring_jobs = paginate(sorted_query)
|
|
8
11
|
|
|
9
12
|
render_page('Recurring Jobs', SolidQueueMonitor::RecurringJobsPresenter.new(@recurring_jobs[:records],
|
|
10
13
|
current_page: @recurring_jobs[:current_page],
|
|
11
14
|
total_pages: @recurring_jobs[:total_pages],
|
|
12
|
-
filters: filter_params
|
|
15
|
+
filters: filter_params,
|
|
16
|
+
sort: sort_params).render)
|
|
13
17
|
end
|
|
14
18
|
end
|
|
15
19
|
end
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class ScheduledJobsController < BaseController
|
|
5
|
+
SORTABLE_COLUMNS = %w[class_name queue_name scheduled_at].freeze
|
|
6
|
+
|
|
5
7
|
def index
|
|
6
|
-
base_query = SolidQueue::ScheduledExecution.includes(:job)
|
|
7
|
-
|
|
8
|
+
base_query = SolidQueue::ScheduledExecution.includes(:job)
|
|
9
|
+
sorted_query = apply_execution_sorting(filter_scheduled_jobs(base_query), SORTABLE_COLUMNS, 'scheduled_at', :asc)
|
|
10
|
+
@scheduled_jobs = paginate(sorted_query)
|
|
8
11
|
|
|
9
12
|
render_page('Scheduled Jobs', SolidQueueMonitor::ScheduledJobsPresenter.new(@scheduled_jobs[:records],
|
|
10
13
|
current_page: @scheduled_jobs[:current_page],
|
|
11
14
|
total_pages: @scheduled_jobs[:total_pages],
|
|
12
|
-
filters: filter_params
|
|
15
|
+
filters: filter_params,
|
|
16
|
+
sort: sort_params).render)
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def create
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidQueueMonitor
|
|
4
|
+
class SearchController < BaseController
|
|
5
|
+
def index
|
|
6
|
+
query = params[:q]
|
|
7
|
+
results = SearchService.new(query).search
|
|
8
|
+
|
|
9
|
+
render_page('Search', SearchResultsPresenter.new(query, results).render, search_query: query)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module SolidQueueMonitor
|
|
4
4
|
class WorkersController < BaseController
|
|
5
|
+
SORTABLE_COLUMNS = %w[hostname last_heartbeat_at].freeze
|
|
6
|
+
|
|
5
7
|
def index
|
|
6
|
-
base_query = SolidQueue::Process.
|
|
7
|
-
|
|
8
|
-
@processes = paginate(
|
|
8
|
+
base_query = SolidQueue::Process.all
|
|
9
|
+
sorted_query = apply_sorting(filter_workers(base_query), SORTABLE_COLUMNS, 'last_heartbeat_at', :desc)
|
|
10
|
+
@processes = paginate(sorted_query)
|
|
9
11
|
|
|
10
12
|
render_page('Workers', SolidQueueMonitor::WorkersPresenter.new(
|
|
11
13
|
@processes[:records],
|
|
12
14
|
current_page: @processes[:current_page],
|
|
13
15
|
total_pages: @processes[:total_pages],
|
|
14
|
-
filters: worker_filter_params
|
|
16
|
+
filters: worker_filter_params,
|
|
17
|
+
sort: sort_params
|
|
15
18
|
).render)
|
|
16
19
|
end
|
|
17
20
|
|
|
@@ -118,6 +118,34 @@ module SolidQueueMonitor
|
|
|
118
118
|
"<a href=\"#{queue_details_path(queue_name: queue_name)}\" class=\"#{classes}\">#{queue_name}</a>"
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
+
def sortable_header(column, label)
|
|
122
|
+
return "<th>#{label}</th>" unless @sort
|
|
123
|
+
|
|
124
|
+
column_str = column.to_s
|
|
125
|
+
is_active = @sort[:sort_by] == column_str
|
|
126
|
+
next_direction = is_active && @sort[:sort_direction] == 'asc' ? 'desc' : 'asc'
|
|
127
|
+
arrow = sort_arrow(is_active)
|
|
128
|
+
css_class = is_active ? 'sortable-header active' : 'sortable-header'
|
|
129
|
+
|
|
130
|
+
"<th><a href=\"?sort_by=#{column}&sort_direction=#{next_direction}#{filter_query_string}\" class=\"#{css_class}\">#{label}#{arrow}</a></th>"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def sort_arrow(is_active)
|
|
134
|
+
return ' ⇅' unless is_active
|
|
135
|
+
|
|
136
|
+
@sort[:sort_direction] == 'asc' ? ' ↑' : ' ↓'
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def filter_query_string
|
|
140
|
+
params = []
|
|
141
|
+
params << "class_name=#{@filters[:class_name]}" if @filters && @filters[:class_name].present?
|
|
142
|
+
params << "queue_name=#{@filters[:queue_name]}" if @filters && @filters[:queue_name].present?
|
|
143
|
+
params << "arguments=#{@filters[:arguments]}" if @filters && @filters[:arguments].present?
|
|
144
|
+
params << "status=#{@filters[:status]}" if @filters && @filters[:status].present?
|
|
145
|
+
|
|
146
|
+
params.empty? ? '' : "&#{params.join('&')}"
|
|
147
|
+
end
|
|
148
|
+
|
|
121
149
|
def request_path
|
|
122
150
|
if defined?(controller) && controller.respond_to?(:request)
|
|
123
151
|
controller.request.path
|
|
@@ -138,14 +166,28 @@ module SolidQueueMonitor
|
|
|
138
166
|
private
|
|
139
167
|
|
|
140
168
|
def query_params
|
|
141
|
-
params =
|
|
142
|
-
params << "class_name=#{@filters[:class_name]}" if @filters && @filters[:class_name].present?
|
|
143
|
-
params << "queue_name=#{@filters[:queue_name]}" if @filters && @filters[:queue_name].present?
|
|
144
|
-
params << "status=#{@filters[:status]}" if @filters && @filters[:status].present?
|
|
145
|
-
|
|
169
|
+
params = build_filter_params + build_sort_params
|
|
146
170
|
params.empty? ? '' : "&#{params.join('&')}"
|
|
147
171
|
end
|
|
148
172
|
|
|
173
|
+
def build_filter_params
|
|
174
|
+
return [] unless @filters
|
|
175
|
+
|
|
176
|
+
filter_keys = %i[class_name queue_name status]
|
|
177
|
+
filter_keys.filter_map do |key|
|
|
178
|
+
"#{key}=#{@filters[key]}" if @filters[key].present?
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def build_sort_params
|
|
183
|
+
return [] unless @sort
|
|
184
|
+
|
|
185
|
+
sort_keys = %i[sort_by sort_direction]
|
|
186
|
+
sort_keys.filter_map do |key|
|
|
187
|
+
"#{key}=#{@sort[key]}" if @sort[key].present?
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
149
191
|
def full_path(route_name, *args)
|
|
150
192
|
SolidQueueMonitor::Engine.routes.url_helpers.send(route_name, *args)
|
|
151
193
|
rescue NoMethodError
|
|
@@ -5,11 +5,12 @@ module SolidQueueMonitor
|
|
|
5
5
|
include Rails.application.routes.url_helpers
|
|
6
6
|
include SolidQueueMonitor::Engine.routes.url_helpers
|
|
7
7
|
|
|
8
|
-
def initialize(jobs, current_page: 1, total_pages: 1, filters: {})
|
|
8
|
+
def initialize(jobs, current_page: 1, total_pages: 1, filters: {}, sort: {})
|
|
9
9
|
@jobs = jobs
|
|
10
10
|
@current_page = current_page
|
|
11
11
|
@total_pages = total_pages
|
|
12
12
|
@filters = filters
|
|
13
|
+
@sort = sort
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def render
|
|
@@ -60,10 +61,11 @@ module SolidQueueMonitor
|
|
|
60
61
|
<thead>
|
|
61
62
|
<tr>
|
|
62
63
|
<th><input type="checkbox" id="select-all" class="select-all-checkbox"></th>
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
#{sortable_header('class_name', 'Job')}
|
|
65
|
+
#{sortable_header('queue_name', 'Queue')}
|
|
65
66
|
<th>Error</th>
|
|
66
67
|
<th>Arguments</th>
|
|
68
|
+
#{sortable_header('created_at', 'Failed At')}
|
|
67
69
|
<th>Actions</th>
|
|
68
70
|
</tr>
|
|
69
71
|
</thead>
|
|
@@ -261,11 +263,9 @@ module SolidQueueMonitor
|
|
|
261
263
|
</td>
|
|
262
264
|
<td>
|
|
263
265
|
<div class="error-message">#{error[:message].to_s.truncate(100)}</div>
|
|
264
|
-
<div class="job-meta">
|
|
265
|
-
<span class="job-timestamp">Failed at: #{format_datetime(failed_execution.created_at)}</span>
|
|
266
|
-
</div>
|
|
267
266
|
</td>
|
|
268
267
|
<td>#{format_arguments(job.arguments)}</td>
|
|
268
|
+
<td>#{format_datetime(failed_execution.created_at)}</td>
|
|
269
269
|
<td class="actions-cell">
|
|
270
270
|
<div class="job-actions">
|
|
271
271
|
<a href="javascript:void(0)"#{' '}
|
|
@@ -4,11 +4,12 @@ module SolidQueueMonitor
|
|
|
4
4
|
class InProgressJobsPresenter < BasePresenter
|
|
5
5
|
include SolidQueueMonitor::Engine.routes.url_helpers
|
|
6
6
|
|
|
7
|
-
def initialize(jobs, current_page: 1, total_pages: 1, filters: {})
|
|
7
|
+
def initialize(jobs, current_page: 1, total_pages: 1, filters: {}, sort: {})
|
|
8
8
|
@jobs = jobs
|
|
9
9
|
@current_page = current_page
|
|
10
10
|
@total_pages = total_pages
|
|
11
11
|
@filters = filters
|
|
12
|
+
@sort = sort
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
def render
|
|
@@ -47,10 +48,10 @@ module SolidQueueMonitor
|
|
|
47
48
|
<table>
|
|
48
49
|
<thead>
|
|
49
50
|
<tr>
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
#{sortable_header('class_name', 'Job')}
|
|
52
|
+
#{sortable_header('queue_name', 'Queue')}
|
|
52
53
|
<th>Arguments</th>
|
|
53
|
-
|
|
54
|
+
#{sortable_header('created_at', 'Started At')}
|
|
54
55
|
<th>Process ID</th>
|
|
55
56
|
</tr>
|
|
56
57
|
</thead>
|