solid_queue_monitor 0.2.0 → 0.3.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +24 -10
  3. data/Rakefile +8 -8
  4. data/app/controllers/solid_queue_monitor/application_controller.rb +25 -0
  5. data/app/controllers/solid_queue_monitor/base_controller.rb +70 -67
  6. data/app/controllers/solid_queue_monitor/failed_jobs_controller.rb +8 -7
  7. data/app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb +16 -10
  8. data/app/controllers/solid_queue_monitor/overview_controller.rb +13 -12
  9. data/app/controllers/solid_queue_monitor/queues_controller.rb +4 -2
  10. data/app/controllers/solid_queue_monitor/ready_jobs_controller.rb +8 -7
  11. data/app/controllers/solid_queue_monitor/recurring_jobs_controller.rb +7 -6
  12. data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +8 -7
  13. data/app/presenters/solid_queue_monitor/base_presenter.rb +42 -41
  14. data/app/presenters/solid_queue_monitor/failed_jobs_presenter.rb +51 -41
  15. data/app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb +13 -2
  16. data/app/presenters/solid_queue_monitor/jobs_presenter.rb +18 -9
  17. data/app/presenters/solid_queue_monitor/queues_presenter.rb +5 -5
  18. data/app/presenters/solid_queue_monitor/ready_jobs_presenter.rb +10 -2
  19. data/app/presenters/solid_queue_monitor/recurring_jobs_presenter.rb +5 -2
  20. data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +15 -9
  21. data/app/presenters/solid_queue_monitor/stats_presenter.rb +3 -1
  22. data/app/services/solid_queue_monitor/authentication_service.rb +7 -5
  23. data/app/services/solid_queue_monitor/execute_job_service.rb +3 -1
  24. data/app/services/solid_queue_monitor/failed_job_service.rb +38 -36
  25. data/app/services/solid_queue_monitor/html_generator.rb +5 -2
  26. data/app/services/solid_queue_monitor/pagination_service.rb +3 -1
  27. data/app/services/solid_queue_monitor/stats_calculator.rb +3 -1
  28. data/app/services/solid_queue_monitor/status_calculator.rb +4 -1
  29. data/app/services/solid_queue_monitor/stylesheet_generator.rb +23 -21
  30. data/config/initializers/solid_queue_monitor.rb +3 -1
  31. data/config/routes.rb +6 -4
  32. data/lib/generators/solid_queue_monitor/install_generator.rb +7 -5
  33. data/lib/generators/solid_queue_monitor/templates/initializer.rb +3 -1
  34. data/lib/solid_queue_monitor/engine.rb +5 -3
  35. data/lib/solid_queue_monitor/version.rb +2 -2
  36. data/lib/solid_queue_monitor.rb +9 -13
  37. data/lib/tasks/app.rake +42 -40
  38. metadata +9 -148
  39. data/app/controllers/solid_queue_monitor/monitor_controller.rb +0 -318
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class ScheduledJobsPresenter < BasePresenter
3
-
4
5
  include Rails.application.routes.url_helpers
5
6
  include SolidQueueMonitor::Engine.routes.url_helpers
6
7
 
@@ -31,13 +32,18 @@ module SolidQueueMonitor
31
32
  <input type="text" name="queue_name" id="queue_name" value="#{@filters[:queue_name]}" placeholder="Filter by queue">
32
33
  </div>
33
34
 
35
+ <div class="filter-group">
36
+ <label for="arguments">Arguments:</label>
37
+ <input type="text" name="arguments" id="arguments" value="#{@filters[:arguments]}" placeholder="Filter by arguments">
38
+ </div>
39
+
34
40
  <div class="filter-actions">
35
41
  <button type="submit" class="filter-button">Apply Filters</button>
36
42
  <a href="#{scheduled_jobs_path}" class="reset-button">Reset</a>
37
43
  </div>
38
44
  </form>
39
45
  </div>
40
-
46
+
41
47
  <div class="bulk-actions-bar">
42
48
  <button type="button" class="action-button execute-button" id="execute-selected-top" disabled>Execute Selected</button>
43
49
  </div>
@@ -55,7 +61,7 @@ module SolidQueueMonitor
55
61
  const jobCheckboxes = document.getElementsByName('job_ids[]');
56
62
  const executeButton = document.getElementById('execute-selected-top');
57
63
  const form = document.getElementById('scheduled-jobs-form');
58
-
64
+ #{' '}
59
65
  selectAllCheckbox.addEventListener('change', function() {
60
66
  jobCheckboxes.forEach(checkbox => checkbox.checked = this.checked);
61
67
  updateExecuteButton();
@@ -67,12 +73,12 @@ module SolidQueueMonitor
67
73
  updateExecuteButton();
68
74
  });
69
75
  });
70
-
76
+ #{' '}
71
77
  // Add event listener for the execute button
72
78
  executeButton.addEventListener('click', function() {
73
79
  const selectedIds = Array.from(document.querySelectorAll('input[name="job_ids[]"]:checked')).map(cb => cb.value);
74
80
  if (selectedIds.length === 0) return;
75
-
81
+ #{' '}
76
82
  // Add selected IDs as hidden inputs
77
83
  selectedIds.forEach(id => {
78
84
  const input = document.createElement('input');
@@ -81,16 +87,16 @@ module SolidQueueMonitor
81
87
  input.value = id;
82
88
  form.appendChild(input);
83
89
  });
84
-
90
+ #{' '}
85
91
  form.submit();
86
92
  });
87
-
93
+ #{' '}
88
94
  function updateExecuteButton() {
89
95
  const checkboxes = document.getElementsByName('job_ids[]');
90
96
  const checked = Array.from(checkboxes).some(cb => cb.checked);
91
97
  executeButton.disabled = !checked;
92
98
  }
93
-
99
+ #{' '}
94
100
  // Initialize button state
95
101
  updateExecuteButton();
96
102
  });
@@ -134,4 +140,4 @@ module SolidQueueMonitor
134
140
  HTML
135
141
  end
136
142
  end
137
- end
143
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class StatsPresenter < BasePresenter
3
5
  def initialize(stats)
@@ -32,4 +34,4 @@ module SolidQueueMonitor
32
34
  HTML
33
35
  end
34
36
  end
35
- end
37
+ end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class AuthenticationService
3
5
  def self.authenticate(username, password)
4
6
  return true unless SolidQueueMonitor.authentication_enabled
5
-
6
- username == SolidQueueMonitor.username &&
7
- password == SolidQueueMonitor.password
7
+
8
+ username == SolidQueueMonitor.username &&
9
+ password == SolidQueueMonitor.password
8
10
  end
9
-
11
+
10
12
  def self.authentication_required?
11
13
  SolidQueueMonitor.authentication_enabled
12
14
  end
13
15
  end
14
- end
16
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class ExecuteJobService
3
5
  def call(id)
@@ -25,4 +27,4 @@ module SolidQueueMonitor
25
27
  end
26
28
  end
27
29
  end
28
- end
30
+ end
@@ -1,47 +1,49 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class FailedJobService
3
5
  def retry_job(failed_execution_id)
4
6
  failed_execution = SolidQueue::FailedExecution.find_by(id: failed_execution_id)
5
- return { success: false, message: "Failed job not found" } unless failed_execution
6
-
7
+ return { success: false, message: 'Failed job not found' } unless failed_execution
8
+
7
9
  job = failed_execution.job
8
- return { success: false, message: "Associated job not found" } unless job
9
-
10
+ return { success: false, message: 'Associated job not found' } unless job
11
+
10
12
  ActiveRecord::Base.transaction do
11
13
  SolidQueue::ReadyExecution.create!(
12
14
  job_id: job.id,
13
15
  queue_name: get_queue_name(failed_execution, job),
14
16
  priority: job.priority
15
17
  )
16
-
18
+
17
19
  failed_execution.destroy!
18
20
  end
19
-
20
- { success: true, message: "Job moved to ready queue for retry" }
21
+
22
+ { success: true, message: 'Job moved to ready queue for retry' }
21
23
  end
22
-
24
+
23
25
  def discard_job(failed_execution_id)
24
26
  failed_execution = SolidQueue::FailedExecution.find_by(id: failed_execution_id)
25
- return { success: false, message: "Failed job not found" } unless failed_execution
26
-
27
+ return { success: false, message: 'Failed job not found' } unless failed_execution
28
+
27
29
  job = failed_execution.job
28
- return { success: false, message: "Associated job not found" } unless job
29
-
30
+ return { success: false, message: 'Associated job not found' } unless job
31
+
30
32
  ActiveRecord::Base.transaction do
31
33
  job.update!(finished_at: Time.current)
32
-
34
+
33
35
  failed_execution.destroy!
34
36
  end
35
-
36
- { success: true, message: "Job has been discarded" }
37
+
38
+ { success: true, message: 'Job has been discarded' }
37
39
  end
38
-
40
+
39
41
  def retry_all(job_ids)
40
- return { success: false, message: "No jobs selected" } if job_ids.blank?
41
-
42
+ return { success: false, message: 'No jobs selected' } if job_ids.blank?
43
+
42
44
  success_count = 0
43
45
  failed_count = 0
44
-
46
+
45
47
  job_ids.each do |id|
46
48
  result = retry_job(id)
47
49
  if result[:success]
@@ -50,22 +52,22 @@ module SolidQueueMonitor
50
52
  failed_count += 1
51
53
  end
52
54
  end
53
-
54
- if success_count > 0 && failed_count == 0
55
- { success: true, message: "All selected jobs have been queued for retry" }
56
- elsif success_count > 0 && failed_count > 0
55
+
56
+ if success_count.positive? && failed_count.zero?
57
+ { success: true, message: 'All selected jobs have been queued for retry' }
58
+ elsif success_count.positive? && failed_count.positive?
57
59
  { success: true, message: "#{success_count} jobs queued for retry, #{failed_count} failed" }
58
60
  else
59
- { success: false, message: "Failed to retry jobs" }
61
+ { success: false, message: 'Failed to retry jobs' }
60
62
  end
61
63
  end
62
-
64
+
63
65
  def discard_all(job_ids)
64
- return { success: false, message: "No jobs selected" } if job_ids.blank?
65
-
66
+ return { success: false, message: 'No jobs selected' } if job_ids.blank?
67
+
66
68
  success_count = 0
67
69
  failed_count = 0
68
-
70
+
69
71
  job_ids.each do |id|
70
72
  result = discard_job(id)
71
73
  if result[:success]
@@ -74,18 +76,18 @@ module SolidQueueMonitor
74
76
  failed_count += 1
75
77
  end
76
78
  end
77
-
78
- if success_count > 0 && failed_count == 0
79
- { success: true, message: "All selected jobs have been discarded" }
80
- elsif success_count > 0 && failed_count > 0
79
+
80
+ if success_count.positive? && failed_count.zero?
81
+ { success: true, message: 'All selected jobs have been discarded' }
82
+ elsif success_count.positive? && failed_count.positive?
81
83
  { success: true, message: "#{success_count} jobs discarded, #{failed_count} failed" }
82
84
  else
83
- { success: false, message: "Failed to discard jobs" }
85
+ { success: false, message: 'Failed to discard jobs' }
84
86
  end
85
87
  end
86
-
88
+
87
89
  private
88
-
90
+
89
91
  def get_queue_name(failed_execution, job)
90
92
  if failed_execution.respond_to?(:queue_name) && failed_execution.queue_name.present?
91
93
  failed_execution.queue_name
@@ -94,4 +96,4 @@ module SolidQueueMonitor
94
96
  end
95
97
  end
96
98
  end
97
- end
99
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class HtmlGenerator
3
5
  include Rails.application.routes.url_helpers
@@ -50,9 +52,10 @@ module SolidQueueMonitor
50
52
  </div>
51
53
  HTML
52
54
  end
53
-
55
+
54
56
  def render_message
55
57
  return '' unless @message
58
+
56
59
  class_name = @message_type == 'success' ? 'message-success' : 'message-error'
57
60
  <<-HTML
58
61
  <div id="flash-message" class="message #{class_name}">#{@message}</div>
@@ -111,4 +114,4 @@ module SolidQueueMonitor
111
114
  { only_path: true }
112
115
  end
113
116
  end
114
- end
117
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class PaginationService
3
5
  def initialize(relation, page, per_page)
@@ -28,4 +30,4 @@ module SolidQueueMonitor
28
30
  @relation.limit(@per_page).offset(offset)
29
31
  end
30
32
  end
31
- end
33
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class StatsCalculator
3
5
  def self.calculate
@@ -13,4 +15,4 @@ module SolidQueueMonitor
13
15
  }
14
16
  end
15
17
  end
16
- end
18
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class StatusCalculator
3
5
  def initialize(job)
@@ -8,7 +10,8 @@ module SolidQueueMonitor
8
10
  return 'completed' if @job.finished_at.present?
9
11
  return 'failed' if @job.failed?
10
12
  return 'scheduled' if @job.scheduled_at&.future?
13
+
11
14
  'pending'
12
15
  end
13
16
  end
14
- end
17
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class StylesheetGenerator
3
5
  def generate
@@ -127,7 +129,7 @@ module SolidQueueMonitor
127
129
  white-space: nowrap;
128
130
  }
129
131
 
130
- .solid_queue_monitor th,
132
+ .solid_queue_monitor th,#{' '}
131
133
  .solid_queue_monitor td {
132
134
  padding: 0.75rem 1rem;
133
135
  text-align: left;
@@ -231,7 +233,7 @@ module SolidQueueMonitor
231
233
  padding: 0.5rem 1rem;
232
234
  font-size: 0.875rem;
233
235
  }
234
-
236
+ #{' '}
235
237
  .solid_queue_monitor .pagination-gap {
236
238
  display: inline-flex;
237
239
  align-items: center;
@@ -359,7 +361,7 @@ module SolidQueueMonitor
359
361
  .solid_queue_monitor .filter-and-actions-container {
360
362
  flex-direction: column;
361
363
  }
362
-
364
+ #{' '}
363
365
  .solid_queue_monitor .bulk-actions-container {
364
366
  width: 100%;
365
367
  }
@@ -429,7 +431,7 @@ module SolidQueueMonitor
429
431
  .solid_queue_monitor .reset-button:hover {
430
432
  background: #e5e7eb;
431
433
  }
432
-
434
+
433
435
  /* Action buttons for retry/discard */
434
436
  .solid_queue_monitor .action-button {
435
437
  padding: 0.5rem 1rem;
@@ -441,50 +443,50 @@ module SolidQueueMonitor
441
443
  border: none;
442
444
  text-decoration: none;
443
445
  }
444
-
446
+
445
447
  .solid_queue_monitor .retry-button {
446
448
  background: #3b82f6;
447
449
  color: white;
448
450
  }
449
-
451
+
450
452
  .solid_queue_monitor .retry-button:hover {
451
453
  background: #2563eb;
452
454
  }
453
-
455
+
454
456
  .solid_queue_monitor .discard-button {
455
457
  background: #ef4444;
456
458
  color: white;
457
459
  }
458
-
460
+
459
461
  .solid_queue_monitor .discard-button:hover {
460
462
  background: #dc2626;
461
463
  }
462
-
464
+
463
465
  .solid_queue_monitor .action-button:disabled {
464
466
  opacity: 0.5;
465
467
  cursor: not-allowed;
466
468
  }
467
-
469
+
468
470
  .solid_queue_monitor .inline-form {
469
471
  display: inline-block;
470
472
  margin-right: 0.5rem;
471
473
  }
472
-
474
+
473
475
  .solid_queue_monitor .actions-cell {
474
476
  white-space: nowrap;
475
477
  }
476
-
478
+
477
479
  .solid_queue_monitor .bulk-actions {
478
480
  display: flex;
479
481
  gap: 0.5rem;
480
482
  }
481
-
483
+
482
484
  .solid_queue_monitor .error-message {
483
485
  color: #dc2626;
484
486
  font-weight: 500;
485
487
  margin-bottom: 0.25rem;
486
488
  }
487
-
489
+
488
490
  .solid_queue_monitor .error-backtrace {
489
491
  font-size: 0.75rem;
490
492
  white-space: pre-wrap;
@@ -495,21 +497,21 @@ module SolidQueueMonitor
495
497
  border-radius: 0.25rem;
496
498
  margin-top: 0.5rem;
497
499
  }
498
-
500
+
499
501
  .solid_queue_monitor details {
500
502
  margin-top: 0.25rem;
501
503
  }
502
-
504
+
503
505
  .solid_queue_monitor summary {
504
506
  cursor: pointer;
505
507
  color: #6b7280;
506
508
  font-size: 0.75rem;
507
509
  }
508
-
510
+
509
511
  .solid_queue_monitor summary:hover {
510
512
  color: #4b5563;
511
513
  }
512
-
514
+
513
515
  .solid_queue_monitor .job-checkbox,
514
516
  .solid_queue_monitor .select-all-checkbox {
515
517
  width: 1rem;
@@ -525,7 +527,7 @@ module SolidQueueMonitor
525
527
  border-radius: 0.5rem;
526
528
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
527
529
  }
528
-
530
+
529
531
  .solid_queue_monitor .bulk-actions-bar .action-button {
530
532
  padding: 0.6rem 1rem;
531
533
  font-size: 0.875rem;
@@ -535,11 +537,11 @@ module SolidQueueMonitor
535
537
  background: var(--primary-color);
536
538
  color: white;
537
539
  }
538
-
540
+
539
541
  .solid_queue_monitor .execute-button:hover {
540
542
  background: #2563eb;
541
543
  }
542
544
  CSS
543
545
  end
544
546
  end
545
- end
547
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  SolidQueueMonitor.setup do |config|
2
4
  config.username = 'admin' # Change this in your application
3
5
  config.password = 'password' # Change this in your application
4
6
  config.jobs_per_page = 25
5
- end
7
+ end
data/config/routes.rb CHANGED
@@ -1,17 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  SolidQueueMonitor::Engine.routes.draw do
2
4
  root to: 'overview#index', as: :root
3
-
5
+
4
6
  resources :ready_jobs, only: [:index]
5
7
  resources :scheduled_jobs, only: [:index]
6
8
  resources :recurring_jobs, only: [:index]
7
9
  resources :failed_jobs, only: [:index]
8
10
  resources :in_progress_jobs, only: [:index]
9
11
  resources :queues, only: [:index]
10
-
12
+
11
13
  post 'execute_jobs', to: 'scheduled_jobs#create', as: :execute_jobs
12
-
14
+
13
15
  post 'retry_failed_job/:id', to: 'failed_jobs#retry', as: :retry_failed_job
14
16
  post 'discard_failed_job/:id', to: 'failed_jobs#discard', as: :discard_failed_job
15
17
  post 'retry_failed_jobs', to: 'failed_jobs#retry_all', as: :retry_failed_jobs
16
18
  post 'discard_failed_jobs', to: 'failed_jobs#discard_all', as: :discard_failed_jobs
17
- end
19
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/generators/base'
2
4
 
3
5
  module SolidQueueMonitor
@@ -6,18 +8,18 @@ module SolidQueueMonitor
6
8
  source_root File.expand_path('templates', __dir__)
7
9
 
8
10
  def copy_initializer
9
- template "initializer.rb", "config/initializers/solid_queue_monitor.rb"
11
+ template 'initializer.rb', 'config/initializers/solid_queue_monitor.rb'
10
12
  end
11
13
 
12
14
  def add_routes
13
- prepend_to_file "config/routes.rb", "require 'solid_queue_monitor'\n\n"
14
-
15
+ prepend_to_file 'config/routes.rb', "require 'solid_queue_monitor'\n\n"
16
+
15
17
  route "mount SolidQueueMonitor::Engine => '/solid_queue'"
16
18
  end
17
19
 
18
20
  def show_readme
19
- readme "README.md" if behavior == :invoke
21
+ readme 'README.md' if behavior == :invoke
20
22
  end
21
23
  end
22
24
  end
23
- end
25
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  SolidQueueMonitor.setup do |config|
2
4
  # Enable or disable authentication
3
5
  # When disabled, no authentication is required to access the monitor
@@ -11,4 +13,4 @@ SolidQueueMonitor.setup do |config|
11
13
 
12
14
  # Number of jobs to display per page
13
15
  # config.jobs_per_page = 25
14
- end
16
+ end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SolidQueueMonitor
2
4
  class Engine < ::Rails::Engine
3
5
  isolate_namespace SolidQueueMonitor
4
6
 
5
7
  config.autoload_paths << root.join('app', 'services')
6
-
8
+
7
9
  # Optional: Add eager loading for production
8
10
  config.eager_load_paths << root.join('app', 'services')
9
11
 
10
- initializer "solid_queue_monitor.assets" do |app|
12
+ initializer 'solid_queue_monitor.assets' do |app|
11
13
  # Optional: Add assets if needed
12
14
  end
13
15
  end
14
- end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueMonitor
4
- VERSION = "0.2.0"
5
- end
4
+ VERSION = '0.3.0'
5
+ end
@@ -1,22 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "solid_queue_monitor/version"
4
- require_relative "solid_queue_monitor/engine"
3
+ require_relative 'solid_queue_monitor/version'
4
+ require_relative 'solid_queue_monitor/engine'
5
5
 
6
6
  module SolidQueueMonitor
7
7
  class Error < StandardError; end
8
- # Configuration options
9
- mattr_accessor :username
10
- @@username = 'admin'
11
-
12
- mattr_accessor :password
13
- @@password = 'password'
14
-
15
- mattr_accessor :jobs_per_page
16
- @@jobs_per_page = 25
8
+ class << self
9
+ attr_accessor :username, :password, :jobs_per_page, :authentication_enabled
10
+ end
17
11
 
18
- mattr_accessor :authentication_enabled
19
- @@authentication_enabled = false
12
+ @username = 'admin'
13
+ @password = 'password'
14
+ @jobs_per_page = 25
15
+ @authentication_enabled = false
20
16
 
21
17
  def self.setup
22
18
  yield self