solid_queue_monitor 0.1.2 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1cadc5702b1a5de9167c64a48abfb6639362a3d4e765a3fb1bdf250f5ed4fa81
4
- data.tar.gz: 9f76e05408872948070221edb908146e134bbf67d6be1bc5fe8e8cbbd6b181ea
3
+ metadata.gz: 264dd6a6542855e5209702c11fbb46c1c267b5c8d48d530887030f924bdb929f
4
+ data.tar.gz: 4d5a33c4152f29d0c3ba4c9fd6c59582e7af9694e85469a78cde9a9d173a4433
5
5
  SHA512:
6
- metadata.gz: c27275ffbfc1c18e3c790abe1c6776f417af6c8684ffd33e392ae94dccb022b6465fe4754154aec86e6c2690f77e5ec674cf77893ce71c34e40a71498d9364e7
7
- data.tar.gz: ebb05f940720c5f10aab733f2632584d274af3c786300ba56d9f208535610d5034bbd3b0e8554d55e9f2244e4c6a460f646ef68ad533f03edab038264d7c2c12
6
+ metadata.gz: 704927588ba7329d988a7533ab70e6b444254abc64a15684fbeccce47221c929c39701abd8fbb02dbebf444c0c3a9521cbcc84260d06d3c6fdafca6e01ad2be5
7
+ data.tar.gz: 8b0a608d0ae89c54719dd5286e9de77cb4145d1f1c3dc7c3f8c1b46a521ae1b2e0ad37d35e7c2796a187122fc38f401b423a46109cc81328cff6805274338357
data/README.md CHANGED
@@ -32,16 +32,16 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
32
32
 
33
33
  ![Dashboard Overview](screenshots/dashboard.png)
34
34
 
35
- ### Recurring Jobs
35
+ ### Failed Jobs
36
36
 
37
- ![Recurring Jobs](screenshots/recurring_jobs.png)
37
+ ![Failed Jobs](screenshots/failed_jobs.png)
38
38
 
39
39
  ## Installation
40
40
 
41
41
  Add this line to your application's Gemfile:
42
42
 
43
43
  ```ruby
44
- gem 'solid_queue_monitor', '~> 0.1.2'
44
+ gem 'solid_queue_monitor', '~> 0.2.0'
45
45
  ```
46
46
 
47
47
  Then execute:
@@ -151,4 +151,4 @@ Everyone interacting in the SolidQueueMonitor project's codebases, issue tracker
151
151
 
152
152
  - [Solid Queue](https://github.com/rails/solid_queue) - The official Rails background job framework
153
153
  - [Rails](https://github.com/rails/rails) - The web application framework
154
- - [ActiveJob](https://github.com/rails/rails/tree/main/activejob) - Rails job framework
154
+ - [ActiveJob](https://github.com/rails/rails/tree/main/activejob) - Rails job frameworkk
@@ -0,0 +1,194 @@
1
+ module SolidQueueMonitor
2
+ class BaseController < ActionController::Base
3
+ include ActionController::HttpAuthentication::Basic::ControllerMethods
4
+ include ActionController::Flash
5
+
6
+ before_action :authenticate, if: -> { SolidQueueMonitor::AuthenticationService.authentication_required? }
7
+ layout false
8
+ skip_before_action :verify_authenticity_token
9
+
10
+ # Define a helper method for setting flash messages
11
+ def set_flash_message(message, type)
12
+ session[:flash_message] = message
13
+ session[:flash_type] = type
14
+ end
15
+
16
+ private
17
+
18
+ def authenticate
19
+ authenticate_or_request_with_http_basic do |username, password|
20
+ SolidQueueMonitor::AuthenticationService.authenticate(username, password)
21
+ end
22
+ end
23
+
24
+ def paginate(relation)
25
+ PaginationService.new(relation, current_page, per_page).paginate
26
+ end
27
+
28
+ def render_page(title, content)
29
+ # Get flash message from session
30
+ message = session[:flash_message]
31
+ message_type = session[:flash_type]
32
+
33
+ # Clear the flash message from session after using it
34
+ session.delete(:flash_message)
35
+ session.delete(:flash_type)
36
+
37
+ html = SolidQueueMonitor::HtmlGenerator.new(
38
+ title: title,
39
+ content: content,
40
+ message: message,
41
+ message_type: message_type
42
+ ).generate
43
+
44
+ render html: html.html_safe
45
+ end
46
+
47
+ def current_page
48
+ (params[:page] || 1).to_i
49
+ end
50
+
51
+ def per_page
52
+ SolidQueueMonitor.jobs_per_page
53
+ end
54
+
55
+ # Preload job statuses to avoid N+1 queries
56
+ def preload_job_statuses(jobs)
57
+ return if jobs.empty?
58
+
59
+ # Get all job IDs
60
+ job_ids = jobs.map(&:id)
61
+
62
+ # Find all failed jobs in a single query
63
+ failed_job_ids = SolidQueue::FailedExecution.where(job_id: job_ids).pluck(:job_id)
64
+
65
+ # Find all scheduled jobs in a single query
66
+ scheduled_job_ids = SolidQueue::ScheduledExecution.where(job_id: job_ids).pluck(:job_id)
67
+
68
+ # Attach the status information to each job
69
+ jobs.each do |job|
70
+ job.instance_variable_set(:@failed, failed_job_ids.include?(job.id))
71
+ job.instance_variable_set(:@scheduled, scheduled_job_ids.include?(job.id))
72
+ end
73
+
74
+ # Define the method to check if a job is failed
75
+ SolidQueue::Job.class_eval do
76
+ def failed?
77
+ if instance_variable_defined?(:@failed)
78
+ @failed
79
+ else
80
+ SolidQueue::FailedExecution.exists?(job_id: id)
81
+ end
82
+ end
83
+
84
+ def scheduled?
85
+ if instance_variable_defined?(:@scheduled)
86
+ @scheduled
87
+ else
88
+ SolidQueue::ScheduledExecution.exists?(job_id: id)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ def filter_jobs(relation)
95
+ relation = relation.where("class_name LIKE ?", "%#{params[:class_name]}%") if params[:class_name].present?
96
+ relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%") if params[:queue_name].present?
97
+
98
+ if params[:status].present?
99
+ case params[:status]
100
+ when 'completed'
101
+ relation = relation.where.not(finished_at: nil)
102
+ when 'failed'
103
+ failed_job_ids = SolidQueue::FailedExecution.pluck(:job_id)
104
+ relation = relation.where(id: failed_job_ids)
105
+ when 'scheduled'
106
+ scheduled_job_ids = SolidQueue::ScheduledExecution.pluck(:job_id)
107
+ relation = relation.where(id: scheduled_job_ids)
108
+ when 'pending'
109
+ # Pending jobs are those that are not completed, failed, or scheduled
110
+ failed_job_ids = SolidQueue::FailedExecution.pluck(:job_id)
111
+ scheduled_job_ids = SolidQueue::ScheduledExecution.pluck(:job_id)
112
+ relation = relation.where(finished_at: nil)
113
+ .where.not(id: failed_job_ids + scheduled_job_ids)
114
+ end
115
+ end
116
+
117
+ relation
118
+ end
119
+
120
+ def filter_ready_jobs(relation)
121
+ return relation unless params[:class_name].present? || params[:queue_name].present?
122
+
123
+ if params[:class_name].present?
124
+ job_ids = SolidQueue::Job.where("class_name LIKE ?", "%#{params[:class_name]}%").pluck(:id)
125
+ relation = relation.where(job_id: job_ids)
126
+ end
127
+
128
+ if params[:queue_name].present?
129
+ relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%")
130
+ end
131
+
132
+ relation
133
+ end
134
+
135
+ def filter_scheduled_jobs(relation)
136
+ return relation unless params[:class_name].present? || params[:queue_name].present?
137
+
138
+ if params[:class_name].present?
139
+ job_ids = SolidQueue::Job.where("class_name LIKE ?", "%#{params[:class_name]}%").pluck(:id)
140
+ relation = relation.where(job_id: job_ids)
141
+ end
142
+
143
+ if params[:queue_name].present?
144
+ relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%")
145
+ end
146
+
147
+ relation
148
+ end
149
+
150
+ def filter_recurring_jobs(relation)
151
+ return relation unless params[:class_name].present? || params[:queue_name].present?
152
+
153
+ if params[:class_name].present?
154
+ relation = relation.where("class_name LIKE ?", "%#{params[:class_name]}%")
155
+ end
156
+
157
+ if params[:queue_name].present?
158
+ relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%")
159
+ end
160
+
161
+ relation
162
+ end
163
+
164
+ def filter_failed_jobs(relation)
165
+ return relation unless params[:class_name].present? || params[:queue_name].present?
166
+
167
+ if params[:class_name].present?
168
+ job_ids = SolidQueue::Job.where("class_name LIKE ?", "%#{params[:class_name]}%").pluck(:id)
169
+ relation = relation.where(job_id: job_ids)
170
+ end
171
+
172
+ if params[:queue_name].present?
173
+ # Check if FailedExecution has queue_name column
174
+ if relation.column_names.include?('queue_name')
175
+ relation = relation.where("queue_name LIKE ?", "%#{params[:queue_name]}%")
176
+ else
177
+ # If not, filter by job's queue_name
178
+ job_ids = SolidQueue::Job.where("queue_name LIKE ?", "%#{params[:queue_name]}%").pluck(:id)
179
+ relation = relation.where(job_id: job_ids)
180
+ end
181
+ end
182
+
183
+ relation
184
+ end
185
+
186
+ def filter_params
187
+ {
188
+ class_name: params[:class_name],
189
+ queue_name: params[:queue_name],
190
+ status: params[:status]
191
+ }
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,62 @@
1
+ module SolidQueueMonitor
2
+ class FailedJobsController < BaseController
3
+ def index
4
+ base_query = SolidQueue::FailedExecution.includes(:job).order(created_at: :desc)
5
+ @failed_jobs = paginate(filter_failed_jobs(base_query))
6
+
7
+ render_page('Failed Jobs', SolidQueueMonitor::FailedJobsPresenter.new(@failed_jobs[:records],
8
+ current_page: @failed_jobs[:current_page],
9
+ total_pages: @failed_jobs[:total_pages],
10
+ filters: filter_params
11
+ ).render)
12
+ end
13
+
14
+ def retry
15
+ id = params[:id]
16
+ service = SolidQueueMonitor::FailedJobService.new
17
+
18
+ if service.retry_job(id)
19
+ set_flash_message("Job #{id} has been queued for retry.", 'success')
20
+ else
21
+ set_flash_message("Failed to retry job #{id}.", 'error')
22
+ end
23
+
24
+ redirect_to params[:redirect_to].present? ? params[:redirect_to] : failed_jobs_path
25
+ end
26
+
27
+ def discard
28
+ id = params[:id]
29
+ service = SolidQueueMonitor::FailedJobService.new
30
+
31
+ if service.discard_job(id)
32
+ set_flash_message("Job #{id} has been discarded.", 'success')
33
+ else
34
+ set_flash_message("Failed to discard job #{id}.", 'error')
35
+ end
36
+
37
+ redirect_to params[:redirect_to].present? ? params[:redirect_to] : failed_jobs_path
38
+ end
39
+
40
+ def retry_all
41
+ result = SolidQueueMonitor::FailedJobService.new.retry_all(params[:job_ids])
42
+
43
+ if result[:success]
44
+ set_flash_message(result[:message], 'success')
45
+ else
46
+ set_flash_message(result[:message], 'error')
47
+ end
48
+ redirect_to failed_jobs_path
49
+ end
50
+
51
+ def discard_all
52
+ result = SolidQueueMonitor::FailedJobService.new.discard_all(params[:job_ids])
53
+
54
+ if result[:success]
55
+ set_flash_message(result[:message], 'success')
56
+ else
57
+ set_flash_message(result[:message], 'error')
58
+ end
59
+ redirect_to failed_jobs_path
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ module SolidQueueMonitor
2
+ class InProgressJobsController < BaseController
3
+ def index
4
+ base_query = SolidQueue::ClaimedExecution.includes(:job).order(created_at: :desc)
5
+ @in_progress_jobs = paginate(filter_in_progress_jobs(base_query))
6
+
7
+ render_page('In Progress Jobs', SolidQueueMonitor::InProgressJobsPresenter.new(@in_progress_jobs[:records],
8
+ current_page: @in_progress_jobs[:current_page],
9
+ total_pages: @in_progress_jobs[:total_pages],
10
+ filters: filter_params
11
+ ).render)
12
+ end
13
+
14
+ private
15
+
16
+ def filter_in_progress_jobs(relation)
17
+ return relation unless params[:class_name].present?
18
+
19
+ if params[:class_name].present?
20
+ job_ids = SolidQueue::Job.where("class_name LIKE ?", "%#{params[:class_name]}%").pluck(:id)
21
+ relation = relation.where(job_id: job_ids)
22
+ end
23
+
24
+ relation
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ module SolidQueueMonitor
2
+ class OverviewController < BaseController
3
+ def index
4
+ @stats = SolidQueueMonitor::StatsCalculator.calculate
5
+
6
+ recent_jobs_query = SolidQueue::Job.order(created_at: :desc).limit(100)
7
+ @recent_jobs = paginate(filter_jobs(recent_jobs_query))
8
+
9
+ preload_job_statuses(@recent_jobs[:records])
10
+
11
+ render_page('Overview', generate_overview_content)
12
+ end
13
+
14
+ private
15
+
16
+ def generate_overview_content
17
+ SolidQueueMonitor::StatsPresenter.new(@stats).render +
18
+ SolidQueueMonitor::JobsPresenter.new(@recent_jobs[:records],
19
+ current_page: @recent_jobs[:current_page],
20
+ total_pages: @recent_jobs[:total_pages],
21
+ filters: filter_params
22
+ ).render
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ module SolidQueueMonitor
2
+ class QueuesController < BaseController
3
+ def index
4
+ @queues = SolidQueue::Job.group(:queue_name)
5
+ .select('queue_name, COUNT(*) as job_count')
6
+ .order('job_count DESC')
7
+
8
+ render_page('Queues', SolidQueueMonitor::QueuesPresenter.new(@queues).render)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module SolidQueueMonitor
2
+ class ReadyJobsController < BaseController
3
+ def index
4
+ base_query = SolidQueue::ReadyExecution.includes(:job).order(created_at: :desc)
5
+ @ready_jobs = paginate(filter_ready_jobs(base_query))
6
+
7
+ render_page('Ready Jobs', SolidQueueMonitor::ReadyJobsPresenter.new(@ready_jobs[:records],
8
+ current_page: @ready_jobs[:current_page],
9
+ total_pages: @ready_jobs[:total_pages],
10
+ filters: filter_params
11
+ ).render)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module SolidQueueMonitor
2
+ class RecurringJobsController < BaseController
3
+ def index
4
+ base_query = filter_recurring_jobs(SolidQueue::RecurringTask.order(:key))
5
+ @recurring_jobs = paginate(base_query)
6
+
7
+ render_page('Recurring Jobs', SolidQueueMonitor::RecurringJobsPresenter.new(@recurring_jobs[:records],
8
+ current_page: @recurring_jobs[:current_page],
9
+ total_pages: @recurring_jobs[:total_pages],
10
+ filters: filter_params
11
+ ).render)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ module SolidQueueMonitor
2
+ class ScheduledJobsController < BaseController
3
+ def index
4
+ base_query = SolidQueue::ScheduledExecution.includes(:job).order(scheduled_at: :asc)
5
+ @scheduled_jobs = paginate(filter_scheduled_jobs(base_query))
6
+
7
+ render_page('Scheduled Jobs', SolidQueueMonitor::ScheduledJobsPresenter.new(@scheduled_jobs[:records],
8
+ current_page: @scheduled_jobs[:current_page],
9
+ total_pages: @scheduled_jobs[:total_pages],
10
+ filters: filter_params
11
+ ).render)
12
+ end
13
+
14
+ def create
15
+ if params[:job_ids].present?
16
+ SolidQueueMonitor::ExecuteJobService.new.execute_many(params[:job_ids])
17
+ set_flash_message('Selected jobs moved to ready queue', 'success')
18
+ else
19
+ set_flash_message('No jobs selected', 'error')
20
+ end
21
+ redirect_to scheduled_jobs_path
22
+ end
23
+ end
24
+ end
@@ -32,8 +32,12 @@ module SolidQueueMonitor
32
32
  end
33
33
 
34
34
  # Page links
35
- (1..total_pages).each do |page|
36
- if page == current_page
35
+ visible_pages = calculate_visible_pages(current_page, total_pages)
36
+
37
+ visible_pages.each do |page|
38
+ if page == :gap
39
+ html += "<span class=\"pagination-gap\">...</span>"
40
+ elsif page == current_page
37
41
  html += "<span class=\"pagination-current\">#{page}</span>"
38
42
  else
39
43
  html += "<a href=\"?page=#{page}#{query_params}\" class=\"pagination-link\">#{page}</a>"
@@ -302,9 +302,5 @@ module SolidQueueMonitor
302
302
  # If there's an error accessing queue_name, fall back to job's queue_name
303
303
  job.queue_name
304
304
  end
305
-
306
- def default_url_options
307
- { only_path: true }
308
- end
309
305
  end
310
306
  end
@@ -0,0 +1,71 @@
1
+ module SolidQueueMonitor
2
+ class InProgressJobsPresenter < BasePresenter
3
+ include SolidQueueMonitor::Engine.routes.url_helpers
4
+
5
+ def initialize(jobs, current_page: 1, total_pages: 1, filters: {})
6
+ @jobs = jobs
7
+ @current_page = current_page
8
+ @total_pages = total_pages
9
+ @filters = filters
10
+ end
11
+
12
+ def render
13
+ section_wrapper('In Progress Jobs', generate_filter_form + generate_table + generate_pagination(@current_page, @total_pages))
14
+ end
15
+
16
+ private
17
+
18
+ def generate_filter_form
19
+ <<-HTML
20
+ <div class="filter-form-container">
21
+ <form method="get" action="#{in_progress_jobs_path}" class="filter-form">
22
+ <div class="filter-group">
23
+ <label for="class_name">Job Class:</label>
24
+ <input type="text" name="class_name" id="class_name" value="#{@filters[:class_name]}" placeholder="Filter by class name">
25
+ </div>
26
+
27
+ <div class="filter-actions">
28
+ <button type="submit" class="filter-button">Apply Filters</button>
29
+ <a href="#{in_progress_jobs_path}" class="reset-button">Reset</a>
30
+ </div>
31
+ </form>
32
+ </div>
33
+ HTML
34
+ end
35
+
36
+ def generate_table
37
+ <<-HTML
38
+ <div class="table-container">
39
+ <table>
40
+ <thead>
41
+ <tr>
42
+ <th>Job</th>
43
+ <th>Started At</th>
44
+ <th>Process ID</th>
45
+ </tr>
46
+ </thead>
47
+ <tbody>
48
+ #{@jobs.map { |execution| generate_row(execution) }.join}
49
+ </tbody>
50
+ </table>
51
+ </div>
52
+ HTML
53
+ end
54
+
55
+ def generate_row(execution)
56
+ job = execution.job
57
+ <<-HTML
58
+ <tr>
59
+ <td>
60
+ <div class="job-class">#{job.class_name}</div>
61
+ <div class="job-meta">
62
+ <span class="job-timestamp">Queued at: #{format_datetime(job.created_at)}</span>
63
+ </div>
64
+ </td>
65
+ <td>#{format_datetime(execution.created_at)}</td>
66
+ <td>#{execution.process_id}</td>
67
+ </tr>
68
+ HTML
69
+ end
70
+ end
71
+ end
@@ -70,6 +70,7 @@ module SolidQueueMonitor
70
70
  <th>Queue</th>
71
71
  <th>Status</th>
72
72
  <th>Created At</th>
73
+ <th>Actions</th>
73
74
  </tr>
74
75
  </thead>
75
76
  <tbody>
@@ -82,15 +83,48 @@ module SolidQueueMonitor
82
83
 
83
84
  def generate_row(job)
84
85
  status = job_status(job)
85
- <<-HTML
86
+
87
+ # Build the row HTML
88
+ row_html = <<-HTML
86
89
  <tr>
87
90
  <td>#{job.id}</td>
88
91
  <td>#{job.class_name}</td>
89
92
  <td>#{job.queue_name}</td>
90
93
  <td><span class='status-badge status-#{status}'>#{status}</span></td>
91
94
  <td>#{format_datetime(job.created_at)}</td>
92
- </tr>
93
95
  HTML
96
+
97
+ # Add actions column only for failed jobs
98
+ if status == 'failed'
99
+ # Find the failed execution record for this job
100
+ failed_execution = SolidQueue::FailedExecution.find_by(job_id: job.id)
101
+
102
+ if failed_execution
103
+ row_html += <<-HTML
104
+ <td class="actions-cell">
105
+ <div class="job-actions">
106
+ <form method="post" action="#{retry_failed_job_path(id: failed_execution.id)}" class="inline-form">
107
+ <input type="hidden" name="redirect_to" value="#{root_path}">
108
+ <button type="submit" class="action-button retry-button">Retry</button>
109
+ </form>
110
+
111
+ <form method="post" action="#{discard_failed_job_path(id: failed_execution.id)}" class="inline-form"
112
+ onsubmit="return confirm('Are you sure you want to discard this job?');">
113
+ <input type="hidden" name="redirect_to" value="#{root_path}">
114
+ <button type="submit" class="action-button discard-button">Discard</button>
115
+ </form>
116
+ </div>
117
+ </td>
118
+ HTML
119
+ else
120
+ row_html += "<td></td>"
121
+ end
122
+ else
123
+ row_html += "<td></td>"
124
+ end
125
+
126
+ row_html += "</tr>"
127
+ row_html
94
128
  end
95
129
 
96
130
  def job_status(job)
@@ -10,12 +10,12 @@ module SolidQueueMonitor
10
10
  <h3>Queue Statistics</h3>
11
11
  <div class="stats">
12
12
  #{generate_stat_card('Total Jobs', @stats[:total_jobs])}
13
- #{generate_stat_card('Unique Queues', @stats[:unique_queues])}
14
13
  #{generate_stat_card('Ready', @stats[:ready])}
14
+ #{generate_stat_card('In Progress', @stats[:in_progress])}
15
15
  #{generate_stat_card('Scheduled', @stats[:scheduled])}
16
+ #{generate_stat_card('Recurring', @stats[:recurring])}
16
17
  #{generate_stat_card('Failed', @stats[:failed])}
17
18
  #{generate_stat_card('Completed', @stats[:completed])}
18
- #{generate_stat_card('Recurring', @stats[:recurring])}
19
19
  </div>
20
20
  </div>
21
21
  HTML
@@ -8,14 +8,12 @@ module SolidQueueMonitor
8
8
  return { success: false, message: "Associated job not found" } unless job
9
9
 
10
10
  ActiveRecord::Base.transaction do
11
- # Create a ready execution for the job
12
11
  SolidQueue::ReadyExecution.create!(
13
12
  job_id: job.id,
14
13
  queue_name: get_queue_name(failed_execution, job),
15
14
  priority: job.priority
16
15
  )
17
16
 
18
- # Delete the failed execution
19
17
  failed_execution.destroy!
20
18
  end
21
19
 
@@ -30,10 +28,8 @@ module SolidQueueMonitor
30
28
  return { success: false, message: "Associated job not found" } unless job
31
29
 
32
30
  ActiveRecord::Base.transaction do
33
- # Mark the job as finished
34
31
  job.update!(finished_at: Time.current)
35
32
 
36
- # Delete the failed execution
37
33
  failed_execution.destroy!
38
34
  end
39
35
 
@@ -91,11 +87,9 @@ module SolidQueueMonitor
91
87
  private
92
88
 
93
89
  def get_queue_name(failed_execution, job)
94
- # Try to get queue_name from failed_execution if the method exists
95
90
  if failed_execution.respond_to?(:queue_name) && failed_execution.queue_name.present?
96
91
  failed_execution.queue_name
97
92
  else
98
- # Fall back to job's queue_name
99
93
  job.queue_name
100
94
  end
101
95
  end
@@ -89,8 +89,9 @@ module SolidQueueMonitor
89
89
  <nav class="navigation">
90
90
  <a href="#{root_path}" class="nav-link">Overview</a>
91
91
  <a href="#{ready_jobs_path}" class="nav-link">Ready Jobs</a>
92
- <a href="#{recurring_jobs_path}" class="nav-link">Recurring Jobs</a>
92
+ <a href="#{in_progress_jobs_path}" class="nav-link">In Progress Jobs</a>
93
93
  <a href="#{scheduled_jobs_path}" class="nav-link">Scheduled Jobs</a>
94
+ <a href="#{recurring_jobs_path}" class="nav-link">Recurring Jobs</a>
94
95
  <a href="#{failed_jobs_path}" class="nav-link">Failed Jobs</a>
95
96
  <a href="#{queues_path}" class="nav-link">Queues</a>
96
97
  </nav>
@@ -7,6 +7,7 @@ module SolidQueueMonitor
7
7
  scheduled: SolidQueue::ScheduledExecution.count,
8
8
  ready: SolidQueue::ReadyExecution.count,
9
9
  failed: SolidQueue::FailedExecution.count,
10
+ in_progress: SolidQueue::ClaimedExecution.count,
10
11
  completed: SolidQueue::Job.where.not(finished_at: nil).count,
11
12
  recurring: SolidQueue::RecurringTask.count
12
13
  }
data/config/routes.rb CHANGED
@@ -1,17 +1,17 @@
1
1
  SolidQueueMonitor::Engine.routes.draw do
2
- root to: 'monitor#index'
2
+ root to: 'overview#index', as: :root
3
3
 
4
- get 'ready_jobs', to: 'monitor#ready_jobs', as: 'ready_jobs'
5
- get 'scheduled_jobs', to: 'monitor#scheduled_jobs', as: 'scheduled_jobs'
6
- get 'failed_jobs', to: 'monitor#failed_jobs', as: 'failed_jobs'
7
- get 'recurring_jobs', to: 'monitor#recurring_jobs', as: 'recurring_jobs'
8
- get 'queues', to: 'monitor#queues', as: 'queues'
4
+ resources :ready_jobs, only: [:index]
5
+ resources :scheduled_jobs, only: [:index]
6
+ resources :recurring_jobs, only: [:index]
7
+ resources :failed_jobs, only: [:index]
8
+ resources :in_progress_jobs, only: [:index]
9
+ resources :queues, only: [:index]
9
10
 
10
- post 'execute_jobs', to: 'monitor#execute_jobs', as: 'execute_jobs'
11
+ post 'execute_jobs', to: 'scheduled_jobs#create', as: :execute_jobs
11
12
 
12
- # Failed job actions
13
- post 'retry_failed_job/:id', to: 'monitor#retry_failed_job', as: 'retry_failed_job'
14
- post 'discard_failed_job/:id', to: 'monitor#discard_failed_job', as: 'discard_failed_job'
15
- post 'retry_failed_jobs', to: 'monitor#retry_failed_jobs', as: 'retry_failed_jobs'
16
- post 'discard_failed_jobs', to: 'monitor#discard_failed_jobs', as: 'discard_failed_jobs'
13
+ post 'retry_failed_job/:id', to: 'failed_jobs#retry', as: :retry_failed_job
14
+ post 'discard_failed_job/:id', to: 'failed_jobs#discard', as: :discard_failed_job
15
+ post 'retry_failed_jobs', to: 'failed_jobs#retry_all', as: :retry_failed_jobs
16
+ post 'discard_failed_jobs', to: 'failed_jobs#discard_all', as: :discard_failed_jobs
17
17
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueMonitor
4
- VERSION = "0.1.2"
5
- end
4
+ VERSION = "0.2.0"
5
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_queue_monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vishal Sadriya
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-18 00:00:00.000000000 Z
10
+ date: 2025-03-28 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -187,9 +187,18 @@ extra_rdoc_files: []
187
187
  files:
188
188
  - README.md
189
189
  - Rakefile
190
+ - app/controllers/solid_queue_monitor/base_controller.rb
191
+ - app/controllers/solid_queue_monitor/failed_jobs_controller.rb
192
+ - app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb
190
193
  - app/controllers/solid_queue_monitor/monitor_controller.rb
194
+ - app/controllers/solid_queue_monitor/overview_controller.rb
195
+ - app/controllers/solid_queue_monitor/queues_controller.rb
196
+ - app/controllers/solid_queue_monitor/ready_jobs_controller.rb
197
+ - app/controllers/solid_queue_monitor/recurring_jobs_controller.rb
198
+ - app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb
191
199
  - app/presenters/solid_queue_monitor/base_presenter.rb
192
200
  - app/presenters/solid_queue_monitor/failed_jobs_presenter.rb
201
+ - app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb
193
202
  - app/presenters/solid_queue_monitor/jobs_presenter.rb
194
203
  - app/presenters/solid_queue_monitor/queues_presenter.rb
195
204
  - app/presenters/solid_queue_monitor/ready_jobs_presenter.rb