solid_queue_monitor 0.1.1 → 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 +4 -4
- data/README.md +30 -4
- data/app/controllers/solid_queue_monitor/base_controller.rb +194 -0
- data/app/controllers/solid_queue_monitor/failed_jobs_controller.rb +62 -0
- data/app/controllers/solid_queue_monitor/in_progress_jobs_controller.rb +27 -0
- data/app/controllers/solid_queue_monitor/monitor_controller.rb +75 -7
- data/app/controllers/solid_queue_monitor/overview_controller.rb +25 -0
- data/app/controllers/solid_queue_monitor/queues_controller.rb +11 -0
- data/app/controllers/solid_queue_monitor/ready_jobs_controller.rb +14 -0
- data/app/controllers/solid_queue_monitor/recurring_jobs_controller.rb +14 -0
- data/app/controllers/solid_queue_monitor/scheduled_jobs_controller.rb +24 -0
- data/app/presenters/solid_queue_monitor/base_presenter.rb +57 -48
- data/app/presenters/solid_queue_monitor/failed_jobs_presenter.rb +257 -21
- data/app/presenters/solid_queue_monitor/in_progress_jobs_presenter.rb +71 -0
- data/app/presenters/solid_queue_monitor/jobs_presenter.rb +36 -2
- data/app/presenters/solid_queue_monitor/scheduled_jobs_presenter.rb +33 -10
- data/app/presenters/solid_queue_monitor/stats_presenter.rb +2 -2
- data/app/services/solid_queue_monitor/failed_job_service.rb +97 -0
- data/app/services/solid_queue_monitor/html_generator.rb +28 -2
- data/app/services/solid_queue_monitor/stats_calculator.rb +1 -0
- data/app/services/solid_queue_monitor/stylesheet_generator.rb +150 -1
- data/config/routes.rb +13 -7
- data/lib/solid_queue_monitor/version.rb +2 -2
- metadata +12 -2
@@ -0,0 +1,97 @@
|
|
1
|
+
module SolidQueueMonitor
|
2
|
+
class FailedJobService
|
3
|
+
def retry_job(failed_execution_id)
|
4
|
+
failed_execution = SolidQueue::FailedExecution.find_by(id: failed_execution_id)
|
5
|
+
return { success: false, message: "Failed job not found" } unless failed_execution
|
6
|
+
|
7
|
+
job = failed_execution.job
|
8
|
+
return { success: false, message: "Associated job not found" } unless job
|
9
|
+
|
10
|
+
ActiveRecord::Base.transaction do
|
11
|
+
SolidQueue::ReadyExecution.create!(
|
12
|
+
job_id: job.id,
|
13
|
+
queue_name: get_queue_name(failed_execution, job),
|
14
|
+
priority: job.priority
|
15
|
+
)
|
16
|
+
|
17
|
+
failed_execution.destroy!
|
18
|
+
end
|
19
|
+
|
20
|
+
{ success: true, message: "Job moved to ready queue for retry" }
|
21
|
+
end
|
22
|
+
|
23
|
+
def discard_job(failed_execution_id)
|
24
|
+
failed_execution = SolidQueue::FailedExecution.find_by(id: failed_execution_id)
|
25
|
+
return { success: false, message: "Failed job not found" } unless failed_execution
|
26
|
+
|
27
|
+
job = failed_execution.job
|
28
|
+
return { success: false, message: "Associated job not found" } unless job
|
29
|
+
|
30
|
+
ActiveRecord::Base.transaction do
|
31
|
+
job.update!(finished_at: Time.current)
|
32
|
+
|
33
|
+
failed_execution.destroy!
|
34
|
+
end
|
35
|
+
|
36
|
+
{ success: true, message: "Job has been discarded" }
|
37
|
+
end
|
38
|
+
|
39
|
+
def retry_all(job_ids)
|
40
|
+
return { success: false, message: "No jobs selected" } if job_ids.blank?
|
41
|
+
|
42
|
+
success_count = 0
|
43
|
+
failed_count = 0
|
44
|
+
|
45
|
+
job_ids.each do |id|
|
46
|
+
result = retry_job(id)
|
47
|
+
if result[:success]
|
48
|
+
success_count += 1
|
49
|
+
else
|
50
|
+
failed_count += 1
|
51
|
+
end
|
52
|
+
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
|
57
|
+
{ success: true, message: "#{success_count} jobs queued for retry, #{failed_count} failed" }
|
58
|
+
else
|
59
|
+
{ success: false, message: "Failed to retry jobs" }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def discard_all(job_ids)
|
64
|
+
return { success: false, message: "No jobs selected" } if job_ids.blank?
|
65
|
+
|
66
|
+
success_count = 0
|
67
|
+
failed_count = 0
|
68
|
+
|
69
|
+
job_ids.each do |id|
|
70
|
+
result = discard_job(id)
|
71
|
+
if result[:success]
|
72
|
+
success_count += 1
|
73
|
+
else
|
74
|
+
failed_count += 1
|
75
|
+
end
|
76
|
+
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
|
81
|
+
{ success: true, message: "#{success_count} jobs discarded, #{failed_count} failed" }
|
82
|
+
else
|
83
|
+
{ success: false, message: "Failed to discard jobs" }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def get_queue_name(failed_execution, job)
|
90
|
+
if failed_execution.respond_to?(:queue_name) && failed_execution.queue_name.present?
|
91
|
+
failed_execution.queue_name
|
92
|
+
else
|
93
|
+
job.queue_name
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -54,7 +54,32 @@ module SolidQueueMonitor
|
|
54
54
|
def render_message
|
55
55
|
return '' unless @message
|
56
56
|
class_name = @message_type == 'success' ? 'message-success' : 'message-error'
|
57
|
-
|
57
|
+
<<-HTML
|
58
|
+
<div id="flash-message" class="message #{class_name}">#{@message}</div>
|
59
|
+
<script>
|
60
|
+
// Automatically hide the flash message after 5 seconds
|
61
|
+
document.addEventListener('DOMContentLoaded', function() {
|
62
|
+
var flashMessage = document.getElementById('flash-message');
|
63
|
+
if (flashMessage) {
|
64
|
+
setTimeout(function() {
|
65
|
+
flashMessage.style.opacity = '1';
|
66
|
+
// Fade out animation
|
67
|
+
var fadeEffect = setInterval(function() {
|
68
|
+
if (!flashMessage.style.opacity) {
|
69
|
+
flashMessage.style.opacity = 1;
|
70
|
+
}
|
71
|
+
if (flashMessage.style.opacity > 0) {
|
72
|
+
flashMessage.style.opacity -= 0.1;
|
73
|
+
} else {
|
74
|
+
clearInterval(fadeEffect);
|
75
|
+
flashMessage.style.display = 'none';
|
76
|
+
}
|
77
|
+
}, 50);
|
78
|
+
}, 5000); // 5 seconds
|
79
|
+
}
|
80
|
+
});
|
81
|
+
</script>
|
82
|
+
HTML
|
58
83
|
end
|
59
84
|
|
60
85
|
def generate_header
|
@@ -64,8 +89,9 @@ module SolidQueueMonitor
|
|
64
89
|
<nav class="navigation">
|
65
90
|
<a href="#{root_path}" class="nav-link">Overview</a>
|
66
91
|
<a href="#{ready_jobs_path}" class="nav-link">Ready Jobs</a>
|
67
|
-
<a href="#{
|
92
|
+
<a href="#{in_progress_jobs_path}" class="nav-link">In Progress Jobs</a>
|
68
93
|
<a href="#{scheduled_jobs_path}" class="nav-link">Scheduled Jobs</a>
|
94
|
+
<a href="#{recurring_jobs_path}" class="nav-link">Recurring Jobs</a>
|
69
95
|
<a href="#{failed_jobs_path}" class="nav-link">Failed Jobs</a>
|
70
96
|
<a href="#{queues_path}" class="nav-link">Queues</a>
|
71
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
|
}
|
@@ -200,6 +200,7 @@ module SolidQueueMonitor
|
|
200
200
|
padding: 1rem;
|
201
201
|
margin-bottom: 1rem;
|
202
202
|
border-radius: 0.375rem;
|
203
|
+
transition: opacity 0.5s ease-in-out;
|
203
204
|
}
|
204
205
|
|
205
206
|
.solid_queue_monitor .message-success {
|
@@ -318,12 +319,50 @@ module SolidQueueMonitor
|
|
318
319
|
}
|
319
320
|
}
|
320
321
|
|
322
|
+
.solid_queue_monitor .filter-and-actions-container {
|
323
|
+
display: flex;
|
324
|
+
justify-content: space-between;
|
325
|
+
align-items: flex-start;
|
326
|
+
gap: 1rem;
|
327
|
+
margin-bottom: 1rem;
|
328
|
+
}
|
329
|
+
|
321
330
|
.solid_queue_monitor .filter-form-container {
|
322
331
|
background: white;
|
323
332
|
padding: 1rem;
|
324
333
|
border-radius: 0.5rem;
|
325
|
-
margin-bottom: 1rem;
|
326
334
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
335
|
+
flex: 3;
|
336
|
+
}
|
337
|
+
|
338
|
+
.solid_queue_monitor .bulk-actions-container {
|
339
|
+
display: flex;
|
340
|
+
flex-direction: row;
|
341
|
+
gap: 0.75rem;
|
342
|
+
padding: 1rem;
|
343
|
+
background: white;
|
344
|
+
border-radius: 0.5rem;
|
345
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
346
|
+
flex: 2;
|
347
|
+
align-items: center;
|
348
|
+
justify-content: center;
|
349
|
+
}
|
350
|
+
|
351
|
+
.solid_queue_monitor .large-button {
|
352
|
+
padding: 0.75rem 1.25rem;
|
353
|
+
font-size: 0.9rem;
|
354
|
+
text-align: center;
|
355
|
+
flex: 1;
|
356
|
+
}
|
357
|
+
|
358
|
+
@media (max-width: 992px) {
|
359
|
+
.solid_queue_monitor .filter-and-actions-container {
|
360
|
+
flex-direction: column;
|
361
|
+
}
|
362
|
+
|
363
|
+
.solid_queue_monitor .bulk-actions-container {
|
364
|
+
width: 100%;
|
365
|
+
}
|
327
366
|
}
|
328
367
|
|
329
368
|
.solid_queue_monitor .filter-form {
|
@@ -390,6 +429,116 @@ module SolidQueueMonitor
|
|
390
429
|
.solid_queue_monitor .reset-button:hover {
|
391
430
|
background: #e5e7eb;
|
392
431
|
}
|
432
|
+
|
433
|
+
/* Action buttons for retry/discard */
|
434
|
+
.solid_queue_monitor .action-button {
|
435
|
+
padding: 0.5rem 1rem;
|
436
|
+
border-radius: 0.375rem;
|
437
|
+
font-size: 0.75rem;
|
438
|
+
font-weight: 500;
|
439
|
+
cursor: pointer;
|
440
|
+
transition: background-color 0.2s;
|
441
|
+
border: none;
|
442
|
+
text-decoration: none;
|
443
|
+
}
|
444
|
+
|
445
|
+
.solid_queue_monitor .retry-button {
|
446
|
+
background: #3b82f6;
|
447
|
+
color: white;
|
448
|
+
}
|
449
|
+
|
450
|
+
.solid_queue_monitor .retry-button:hover {
|
451
|
+
background: #2563eb;
|
452
|
+
}
|
453
|
+
|
454
|
+
.solid_queue_monitor .discard-button {
|
455
|
+
background: #ef4444;
|
456
|
+
color: white;
|
457
|
+
}
|
458
|
+
|
459
|
+
.solid_queue_monitor .discard-button:hover {
|
460
|
+
background: #dc2626;
|
461
|
+
}
|
462
|
+
|
463
|
+
.solid_queue_monitor .action-button:disabled {
|
464
|
+
opacity: 0.5;
|
465
|
+
cursor: not-allowed;
|
466
|
+
}
|
467
|
+
|
468
|
+
.solid_queue_monitor .inline-form {
|
469
|
+
display: inline-block;
|
470
|
+
margin-right: 0.5rem;
|
471
|
+
}
|
472
|
+
|
473
|
+
.solid_queue_monitor .actions-cell {
|
474
|
+
white-space: nowrap;
|
475
|
+
}
|
476
|
+
|
477
|
+
.solid_queue_monitor .bulk-actions {
|
478
|
+
display: flex;
|
479
|
+
gap: 0.5rem;
|
480
|
+
}
|
481
|
+
|
482
|
+
.solid_queue_monitor .error-message {
|
483
|
+
color: #dc2626;
|
484
|
+
font-weight: 500;
|
485
|
+
margin-bottom: 0.25rem;
|
486
|
+
}
|
487
|
+
|
488
|
+
.solid_queue_monitor .error-backtrace {
|
489
|
+
font-size: 0.75rem;
|
490
|
+
white-space: pre-wrap;
|
491
|
+
max-height: 200px;
|
492
|
+
overflow-y: auto;
|
493
|
+
background: #f3f4f6;
|
494
|
+
padding: 0.5rem;
|
495
|
+
border-radius: 0.25rem;
|
496
|
+
margin-top: 0.5rem;
|
497
|
+
}
|
498
|
+
|
499
|
+
.solid_queue_monitor details {
|
500
|
+
margin-top: 0.25rem;
|
501
|
+
}
|
502
|
+
|
503
|
+
.solid_queue_monitor summary {
|
504
|
+
cursor: pointer;
|
505
|
+
color: #6b7280;
|
506
|
+
font-size: 0.75rem;
|
507
|
+
}
|
508
|
+
|
509
|
+
.solid_queue_monitor summary:hover {
|
510
|
+
color: #4b5563;
|
511
|
+
}
|
512
|
+
|
513
|
+
.solid_queue_monitor .job-checkbox,
|
514
|
+
.solid_queue_monitor .select-all-checkbox {
|
515
|
+
width: 1rem;
|
516
|
+
height: 1rem;
|
517
|
+
}
|
518
|
+
|
519
|
+
.solid_queue_monitor .bulk-actions-bar {
|
520
|
+
display: flex;
|
521
|
+
gap: 0.75rem;
|
522
|
+
margin: 1rem 0;
|
523
|
+
background: white;
|
524
|
+
padding: 0.75rem;
|
525
|
+
border-radius: 0.5rem;
|
526
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
527
|
+
}
|
528
|
+
|
529
|
+
.solid_queue_monitor .bulk-actions-bar .action-button {
|
530
|
+
padding: 0.6rem 1rem;
|
531
|
+
font-size: 0.875rem;
|
532
|
+
}
|
533
|
+
|
534
|
+
.solid_queue_monitor .execute-button {
|
535
|
+
background: var(--primary-color);
|
536
|
+
color: white;
|
537
|
+
}
|
538
|
+
|
539
|
+
.solid_queue_monitor .execute-button:hover {
|
540
|
+
background: #2563eb;
|
541
|
+
}
|
393
542
|
CSS
|
394
543
|
end
|
395
544
|
end
|
data/config/routes.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
SolidQueueMonitor::Engine.routes.draw do
|
2
|
-
root to: '
|
2
|
+
root to: 'overview#index', as: :root
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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: '
|
11
|
+
post 'execute_jobs', to: 'scheduled_jobs#create', as: :execute_jobs
|
12
|
+
|
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
|
11
17
|
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.
|
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-
|
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
|
@@ -198,6 +207,7 @@ files:
|
|
198
207
|
- app/presenters/solid_queue_monitor/stats_presenter.rb
|
199
208
|
- app/services/solid_queue_monitor/authentication_service.rb
|
200
209
|
- app/services/solid_queue_monitor/execute_job_service.rb
|
210
|
+
- app/services/solid_queue_monitor/failed_job_service.rb
|
201
211
|
- app/services/solid_queue_monitor/html_generator.rb
|
202
212
|
- app/services/solid_queue_monitor/pagination_service.rb
|
203
213
|
- app/services/solid_queue_monitor/stats_calculator.rb
|