solid_queue_monitor 0.3.1 → 0.4.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: 9866a9be02d283c7d46d4b9097e4b1a5ad33e3d9817f25f026bd45ac15f9585b
4
- data.tar.gz: 2e4fa6b8ec14ad8297a5020ec607c5c261e3093b77c0deaac66b53a541de8fed
3
+ metadata.gz: 3bed1180e7bb59bb8741c6fd8607cfcec9e9b604143f4f96c3d4e6f91ae045c6
4
+ data.tar.gz: 5fe88544f47d2146ec5e8034aa476032bba8fbfae2fbb7bf5aa283b66650dfb8
5
5
  SHA512:
6
- metadata.gz: 4cbcab451ad976ea10d098d3f624e636088a8ab22dc4600b9e92cf77781b227bcc496cc1fc4e0c2fea85b8826f8adff1ca93a78ee9f139b5b1791f2c25c4c90b
7
- data.tar.gz: 8211a5d9500a91e3a3b72b43d80d2f104b2f9e20040c4bf6f8816125e0f2d68bf4d43e4ebd2f97d39422868663190d8d216157a2096cd653af521c266fba989f
6
+ metadata.gz: c59ad8d48e9414e12e86d848f029b9ad31437b6f41c842b787876ce1ae4a348d35ea6808904305c3212ec29124783b02cc7896a039a0ba7e43625842b127c552
7
+ data.tar.gz: 55554c085582dd57bd884b50bfebaa59e2dffa5758646e49e2744e83ea28b4c7d23d347335c5cd08df0909e312d926a6070371e5474911d52d93256585d7e96f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vishal Sadriya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -18,13 +18,14 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
18
18
  - **Dashboard Overview**: Get a quick snapshot of your queue's health with statistics on all job types
19
19
  - **Ready Jobs**: View jobs that are ready to be executed
20
20
  - **In Progress Jobs**: Monitor jobs currently being processed by workers
21
- - **Scheduled Jobs**: See upcoming jobs scheduled for future execution
21
+ - **Scheduled Jobs**: See upcoming jobs scheduled for future execution with ability to execute immediately or reject permanently
22
22
  - **Recurring Jobs**: Manage periodic jobs that run on a schedule
23
23
  - **Failed Jobs**: Track and debug failed jobs, with the ability to retry or discard them
24
24
  - **Queue Management**: View and filter jobs by queue
25
25
  - **Advanced Job Filtering**: Filter jobs by class name, queue, status, and job arguments
26
- - **Quick Actions**: Retry or discard failed jobs directly from any view
26
+ - **Quick Actions**: Retry or discard failed jobs, execute or reject scheduled jobs directly from any view
27
27
  - **Performance Optimized**: Designed for high-volume applications with smart pagination
28
+ - **Auto-refresh**: Real-time monitoring with configurable auto-refresh interval and toggle
28
29
  - **Optional Authentication**: Secure your dashboard with HTTP Basic Authentication
29
30
  - **Responsive Design**: Works on desktop and mobile devices
30
31
  - **Zero Dependencies**: No additional JavaScript libraries or frameworks required
@@ -44,7 +45,7 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
44
45
  Add this line to your application's Gemfile:
45
46
 
46
47
  ```ruby
47
- gem 'solid_queue_monitor', '~> 0.3.0'
48
+ gem 'solid_queue_monitor', '~> 0.4.0'
48
49
  ```
49
50
 
50
51
  Then execute:
@@ -83,6 +84,13 @@ SolidQueueMonitor.setup do |config|
83
84
 
84
85
  # Number of jobs to display per page
85
86
  config.jobs_per_page = 25
87
+
88
+ # Auto-refresh settings
89
+ # Enable or disable auto-refresh globally (users can still toggle it in the UI)
90
+ config.auto_refresh_enabled = true
91
+
92
+ # Auto-refresh interval in seconds (default: 30)
93
+ config.auto_refresh_interval = 30
86
94
  end
87
95
  ```
88
96
 
@@ -103,9 +111,9 @@ The dashboard provides several views:
103
111
 
104
112
  - **Overview**: Shows statistics and recent jobs
105
113
  - **Ready Jobs**: Jobs that are ready to be executed
106
- - **Scheduled Jobs**: Jobs scheduled for future execution
114
+ - **Scheduled Jobs**: Jobs scheduled for future execution with execute and reject actions
107
115
  - **Recurring Jobs**: Jobs that run on a recurring schedule
108
- - **Failed Jobs**: Jobs that have failed with error details
116
+ - **Failed Jobs**: Jobs that have failed with error details and retry/discard actions
109
117
  - **Queues**: Distribution of jobs across different queues
110
118
 
111
119
  ### API-only Applications
@@ -127,7 +135,7 @@ This makes it easy to find specific jobs when debugging issues in your applicati
127
135
 
128
136
  - **Production Monitoring**: Keep an eye on your background job processing in production environments
129
137
  - **Debugging**: Quickly identify and troubleshoot failed jobs
130
- - **Job Management**: Execute scheduled jobs on demand when needed
138
+ - **Job Management**: Execute scheduled jobs on demand or reject unwanted jobs permanently
131
139
  - **Performance Analysis**: Track job distribution and identify bottlenecks
132
140
  - **DevOps Integration**: Easily integrate with your monitoring stack
133
141
 
@@ -21,5 +21,16 @@ module SolidQueueMonitor
21
21
  end
22
22
  redirect_to scheduled_jobs_path
23
23
  end
24
+
25
+ def reject_all
26
+ result = SolidQueueMonitor::RejectJobService.new.reject_many(params[:job_ids])
27
+
28
+ if result[:success]
29
+ set_flash_message(result[:message], 'success')
30
+ else
31
+ set_flash_message(result[:message], 'error')
32
+ end
33
+ redirect_to scheduled_jobs_path
34
+ end
24
35
  end
25
36
  end
@@ -46,13 +46,14 @@ module SolidQueueMonitor
46
46
 
47
47
  <div class="bulk-actions-bar">
48
48
  <button type="button" class="action-button execute-button" id="execute-selected-top" disabled>Execute Selected</button>
49
+ <button type="button" class="action-button discard-button" id="reject-selected-top" disabled>Reject Selected</button>
49
50
  </div>
50
51
  HTML
51
52
  end
52
53
 
53
54
  def generate_table_with_actions
54
55
  <<-HTML
55
- <form id="scheduled-jobs-form" action="#{execute_jobs_path}" method="POST">
56
+ <form id="scheduled-jobs-form" method="POST">
56
57
  #{generate_table}
57
58
  </form>
58
59
  <script>
@@ -60,17 +61,18 @@ module SolidQueueMonitor
60
61
  const selectAllCheckbox = document.querySelector('th input[type="checkbox"]');
61
62
  const jobCheckboxes = document.getElementsByName('job_ids[]');
62
63
  const executeButton = document.getElementById('execute-selected-top');
64
+ const rejectButton = document.getElementById('reject-selected-top');
63
65
  const form = document.getElementById('scheduled-jobs-form');
64
66
  #{' '}
65
67
  selectAllCheckbox.addEventListener('change', function() {
66
68
  jobCheckboxes.forEach(checkbox => checkbox.checked = this.checked);
67
- updateExecuteButton();
69
+ updateButtonStates();
68
70
  });
69
71
 
70
72
  jobCheckboxes.forEach(checkbox => {
71
73
  checkbox.addEventListener('change', function() {
72
74
  selectAllCheckbox.checked = Array.from(jobCheckboxes).every(cb => cb.checked);
73
- updateExecuteButton();
75
+ updateButtonStates();
74
76
  });
75
77
  });
76
78
  #{' '}
@@ -79,6 +81,31 @@ module SolidQueueMonitor
79
81
  const selectedIds = Array.from(document.querySelectorAll('input[name="job_ids[]"]:checked')).map(cb => cb.value);
80
82
  if (selectedIds.length === 0) return;
81
83
  #{' '}
84
+ submitForm('#{execute_jobs_path}', selectedIds);
85
+ });
86
+ #{' '}
87
+ // Add event listener for the reject button
88
+ rejectButton.addEventListener('click', function() {
89
+ const selectedIds = Array.from(document.querySelectorAll('input[name="job_ids[]"]:checked')).map(cb => cb.value);
90
+ if (selectedIds.length === 0) return;
91
+ #{' '}
92
+ if (confirm('Are you sure you want to reject the selected jobs? This action cannot be undone.')) {
93
+ submitForm('#{reject_jobs_path}', selectedIds);
94
+ }
95
+ });
96
+ #{' '}
97
+ function submitForm(actionUrl, selectedIds) {
98
+ // Uncheck all checkboxes to prevent duplicate submission
99
+ document.querySelectorAll('input[name="job_ids[]"]').forEach(checkbox => {
100
+ checkbox.checked = false;
101
+ });
102
+
103
+ // Clear any existing hidden inputs
104
+ document.querySelectorAll('input[type="hidden"][name="job_ids[]"]').forEach(input => input.remove());
105
+
106
+ // Set form action
107
+ form.action = actionUrl;
108
+
82
109
  // Add selected IDs as hidden inputs
83
110
  selectedIds.forEach(id => {
84
111
  const input = document.createElement('input');
@@ -87,18 +114,20 @@ module SolidQueueMonitor
87
114
  input.value = id;
88
115
  form.appendChild(input);
89
116
  });
90
- #{' '}
117
+
118
+ // Submit the form
91
119
  form.submit();
92
- });
120
+ }
93
121
  #{' '}
94
- function updateExecuteButton() {
122
+ function updateButtonStates() {
95
123
  const checkboxes = document.getElementsByName('job_ids[]');
96
124
  const checked = Array.from(checkboxes).some(cb => cb.checked);
97
125
  executeButton.disabled = !checked;
126
+ rejectButton.disabled = !checked;
98
127
  }
99
128
  #{' '}
100
- // Initialize button state
101
- updateExecuteButton();
129
+ // Initialize button states
130
+ updateButtonStates();
102
131
  });
103
132
  </script>
104
133
  HTML
@@ -130,7 +159,7 @@ module SolidQueueMonitor
130
159
  <<-HTML
131
160
  <tr>
132
161
  <td>
133
- <input type="checkbox" name="job_ids[]" value="#{execution.id}" onchange="updateExecuteButton()">
162
+ <input type="checkbox" name="job_ids[]" value="#{execution.id}">
134
163
  </td>
135
164
  <td>#{execution.job.class_name}</td>
136
165
  <td>#{execution.queue_name}</td>
@@ -50,6 +50,7 @@ module SolidQueueMonitor
50
50
  </div>
51
51
  #{generate_footer}
52
52
  </div>
53
+ #{generate_auto_refresh_script}
53
54
  HTML
54
55
  end
55
56
 
@@ -88,7 +89,10 @@ module SolidQueueMonitor
88
89
  def generate_header
89
90
  <<-HTML
90
91
  <header>
91
- <h1>Solid Queue Monitor</h1>
92
+ <div class="header-top">
93
+ <h1>Solid Queue Monitor</h1>
94
+ #{generate_auto_refresh_controls}
95
+ </div>
92
96
  <nav class="navigation">
93
97
  <a href="#{root_path}" class="nav-link">Overview</a>
94
98
  <a href="#{ready_jobs_path}" class="nav-link">Ready Jobs</a>
@@ -110,6 +114,104 @@ module SolidQueueMonitor
110
114
  HTML
111
115
  end
112
116
 
117
+ def generate_auto_refresh_controls
118
+ return '' unless SolidQueueMonitor.auto_refresh_enabled
119
+
120
+ interval = SolidQueueMonitor.auto_refresh_interval
121
+ <<-HTML
122
+ <div class="auto-refresh-container" title="Auto-refresh every #{interval}s" data-tooltip="Auto-refresh: Dashboard updates automatically every #{interval} seconds. Toggle to enable/disable.">
123
+ <span class="auto-refresh-indicator" id="auto-refresh-indicator"></span>
124
+ <span class="auto-refresh-countdown" id="auto-refresh-countdown">#{interval}s</span>
125
+ <label class="auto-refresh-switch" title="Toggle auto-refresh">
126
+ <input type="checkbox" id="auto-refresh-toggle" checked>
127
+ <span class="switch-slider"></span>
128
+ </label>
129
+ <button class="refresh-now-btn" id="refresh-now-btn" title="Refresh now">
130
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
131
+ <path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16"/>
132
+ </svg>
133
+ </button>
134
+ </div>
135
+ HTML
136
+ end
137
+
138
+ def generate_auto_refresh_script
139
+ return '' unless SolidQueueMonitor.auto_refresh_enabled
140
+
141
+ "<script>#{auto_refresh_javascript}</script>"
142
+ end
143
+
144
+ def auto_refresh_javascript
145
+ interval = SolidQueueMonitor.auto_refresh_interval
146
+ <<-JS
147
+ (function() {
148
+ var REFRESH_INTERVAL = #{interval};
149
+ var countdown = REFRESH_INTERVAL;
150
+ var timerId = null;
151
+ var isEnabled = localStorage.getItem('sqm_auto_refresh') !== 'false';
152
+ #{auto_refresh_dom_elements}
153
+ #{auto_refresh_functions}
154
+ #{auto_refresh_event_listeners}
155
+ #{auto_refresh_init}
156
+ })();
157
+ JS
158
+ end
159
+
160
+ def auto_refresh_dom_elements
161
+ <<-JS
162
+ var toggle = document.getElementById('auto-refresh-toggle');
163
+ var indicator = document.getElementById('auto-refresh-indicator');
164
+ var countdownEl = document.getElementById('auto-refresh-countdown');
165
+ var refreshBtn = document.getElementById('refresh-now-btn');
166
+ JS
167
+ end
168
+
169
+ def auto_refresh_functions
170
+ <<-JS
171
+ function updateUI() {
172
+ if (toggle) toggle.checked = isEnabled;
173
+ if (indicator) indicator.classList.toggle('active', isEnabled);
174
+ if (countdownEl) {
175
+ countdownEl.textContent = countdown + 's';
176
+ countdownEl.style.opacity = isEnabled ? '1' : '0.4';
177
+ }
178
+ }
179
+ function tick() {
180
+ countdown--;
181
+ if (countdown <= 0) { refresh(); } else { updateUI(); }
182
+ }
183
+ function startTimer() {
184
+ stopTimer();
185
+ countdown = REFRESH_INTERVAL;
186
+ updateUI();
187
+ timerId = setInterval(tick, 1000);
188
+ }
189
+ function stopTimer() {
190
+ if (timerId) { clearInterval(timerId); timerId = null; }
191
+ }
192
+ function refresh() { window.location.reload(); }
193
+ function setEnabled(enabled) {
194
+ isEnabled = enabled;
195
+ localStorage.setItem('sqm_auto_refresh', enabled ? 'true' : 'false');
196
+ if (enabled) { startTimer(); } else { stopTimer(); countdown = REFRESH_INTERVAL; updateUI(); }
197
+ }
198
+ JS
199
+ end
200
+
201
+ def auto_refresh_event_listeners
202
+ <<-JS
203
+ if (toggle) { toggle.addEventListener('change', function() { setEnabled(this.checked); }); }
204
+ if (refreshBtn) { refreshBtn.addEventListener('click', function() { refresh(); }); }
205
+ JS
206
+ end
207
+
208
+ def auto_refresh_init
209
+ <<-JS
210
+ updateUI();
211
+ if (isEnabled) { startTimer(); }
212
+ JS
213
+ end
214
+
113
215
  def default_url_options
114
216
  { only_path: true }
115
217
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueMonitor
4
+ class RejectJobService
5
+ def call(id)
6
+ execution = SolidQueue::ScheduledExecution.find(id)
7
+ reject_job(execution)
8
+ end
9
+
10
+ def reject_many(ids)
11
+ return { success: false, message: 'No jobs selected' } if ids.blank?
12
+
13
+ success_count = 0
14
+ failed_count = 0
15
+
16
+ ids.each do |id|
17
+ execution = SolidQueue::ScheduledExecution.find_by(id: id)
18
+ if execution
19
+ reject_job(execution)
20
+ success_count += 1
21
+ else
22
+ failed_count += 1
23
+ end
24
+ rescue StandardError
25
+ failed_count += 1
26
+ end
27
+
28
+ if success_count.positive? && failed_count.zero?
29
+ { success: true, message: 'All selected jobs have been rejected' }
30
+ elsif success_count.positive? && failed_count.positive?
31
+ { success: true, message: "#{success_count} jobs rejected, #{failed_count} failed" }
32
+ else
33
+ { success: false, message: 'Failed to reject jobs' }
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def reject_job(execution)
40
+ ActiveRecord::Base.transaction do
41
+ # Mark the associated job as finished to indicate it was rejected
42
+ execution.job.update!(finished_at: Time.current)
43
+
44
+ # Remove the scheduled execution
45
+ execution.destroy
46
+ end
47
+ end
48
+ end
49
+ end
@@ -585,6 +585,183 @@ module SolidQueueMonitor
585
585
  .solid_queue_monitor .execute-button:hover {
586
586
  background: #2563eb;
587
587
  }
588
+
589
+ /* Header top row with title and auto-refresh */
590
+ .solid_queue_monitor .header-top {
591
+ display: flex;
592
+ justify-content: space-between;
593
+ align-items: center;
594
+ margin-bottom: 0.5rem;
595
+ }
596
+
597
+ /* Auto-refresh styles - compact design */
598
+ .solid_queue_monitor .auto-refresh-container {
599
+ position: relative;
600
+ display: flex;
601
+ align-items: center;
602
+ gap: 0.5rem;
603
+ padding: 0.375rem 0.625rem;
604
+ background: white;
605
+ border-radius: 2rem;
606
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
607
+ font-size: 0.75rem;
608
+ color: #6b7280;
609
+ cursor: default;
610
+ }
611
+
612
+ /* Tooltip styles */
613
+ .solid_queue_monitor .auto-refresh-container::after {
614
+ content: attr(data-tooltip);
615
+ position: absolute;
616
+ top: calc(100% + 8px);
617
+ right: 0;
618
+ background: #1f2937;
619
+ color: white;
620
+ padding: 0.5rem 0.75rem;
621
+ border-radius: 0.375rem;
622
+ font-size: 0.75rem;
623
+ line-height: 1.4;
624
+ white-space: nowrap;
625
+ max-width: 280px;
626
+ white-space: normal;
627
+ text-align: left;
628
+ opacity: 0;
629
+ visibility: hidden;
630
+ transition: opacity 0.2s, visibility 0.2s;
631
+ z-index: 1000;
632
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
633
+ pointer-events: none;
634
+ }
635
+
636
+ /* Tooltip arrow */
637
+ .solid_queue_monitor .auto-refresh-container::before {
638
+ content: "";
639
+ position: absolute;
640
+ top: calc(100% + 2px);
641
+ right: 16px;
642
+ border: 6px solid transparent;
643
+ border-bottom-color: #1f2937;
644
+ opacity: 0;
645
+ visibility: hidden;
646
+ transition: opacity 0.2s, visibility 0.2s;
647
+ z-index: 1001;
648
+ pointer-events: none;
649
+ }
650
+
651
+ .solid_queue_monitor .auto-refresh-container:hover::after,
652
+ .solid_queue_monitor .auto-refresh-container:hover::before {
653
+ opacity: 1;
654
+ visibility: visible;
655
+ }
656
+
657
+ .solid_queue_monitor .auto-refresh-indicator {
658
+ width: 6px;
659
+ height: 6px;
660
+ border-radius: 50%;
661
+ background: #d1d5db;
662
+ flex-shrink: 0;
663
+ }
664
+
665
+ .solid_queue_monitor .auto-refresh-indicator.active {
666
+ background: var(--success-color);
667
+ animation: pulse 2s infinite;
668
+ }
669
+
670
+ @keyframes pulse {
671
+ 0%, 100% { opacity: 1; }
672
+ 50% { opacity: 0.5; }
673
+ }
674
+
675
+ .solid_queue_monitor .auto-refresh-countdown {
676
+ font-variant-numeric: tabular-nums;
677
+ font-weight: 500;
678
+ min-width: 1.75rem;
679
+ color: var(--text-color);
680
+ transition: opacity 0.2s;
681
+ }
682
+
683
+ /* Toggle switch */
684
+ .solid_queue_monitor .auto-refresh-switch {
685
+ position: relative;
686
+ display: inline-block;
687
+ width: 32px;
688
+ height: 18px;
689
+ flex-shrink: 0;
690
+ }
691
+
692
+ .solid_queue_monitor .auto-refresh-switch input {
693
+ opacity: 0;
694
+ width: 0;
695
+ height: 0;
696
+ }
697
+
698
+ .solid_queue_monitor .switch-slider {
699
+ position: absolute;
700
+ cursor: pointer;
701
+ top: 0;
702
+ left: 0;
703
+ right: 0;
704
+ bottom: 0;
705
+ background-color: #d1d5db;
706
+ transition: 0.2s;
707
+ border-radius: 18px;
708
+ }
709
+
710
+ .solid_queue_monitor .switch-slider:before {
711
+ position: absolute;
712
+ content: "";
713
+ height: 14px;
714
+ width: 14px;
715
+ left: 2px;
716
+ bottom: 2px;
717
+ background-color: white;
718
+ transition: 0.2s;
719
+ border-radius: 50%;
720
+ box-shadow: 0 1px 2px rgba(0,0,0,0.2);
721
+ }
722
+
723
+ .solid_queue_monitor .auto-refresh-switch input:checked + .switch-slider {
724
+ background-color: var(--success-color);
725
+ }
726
+
727
+ .solid_queue_monitor .auto-refresh-switch input:checked + .switch-slider:before {
728
+ transform: translateX(14px);
729
+ }
730
+
731
+ .solid_queue_monitor .refresh-now-btn {
732
+ display: flex;
733
+ align-items: center;
734
+ justify-content: center;
735
+ background: transparent;
736
+ border: none;
737
+ padding: 0.25rem;
738
+ border-radius: 0.25rem;
739
+ cursor: pointer;
740
+ color: #9ca3af;
741
+ transition: all 0.2s;
742
+ }
743
+
744
+ .solid_queue_monitor .refresh-now-btn:hover {
745
+ color: var(--primary-color);
746
+ background: rgba(59, 130, 246, 0.1);
747
+ }
748
+
749
+ @media (max-width: 768px) {
750
+ .solid_queue_monitor .header-top {
751
+ flex-direction: column;
752
+ gap: 0.75rem;
753
+ }
754
+
755
+ .solid_queue_monitor .auto-refresh-container {
756
+ align-self: center;
757
+ }
758
+
759
+ /* Hide tooltip on mobile - use native title instead */
760
+ .solid_queue_monitor .auto-refresh-container::after,
761
+ .solid_queue_monitor .auto-refresh-container::before {
762
+ display: none;
763
+ }
764
+ }
588
765
  CSS
589
766
  end
590
767
  end
@@ -4,4 +4,6 @@ SolidQueueMonitor.setup do |config|
4
4
  config.username = 'admin' # Change this in your application
5
5
  config.password = 'password' # Change this in your application
6
6
  config.jobs_per_page = 25
7
+ config.auto_refresh_enabled = true # Enable/disable auto-refresh globally
8
+ config.auto_refresh_interval = 30 # Auto-refresh interval in seconds
7
9
  end
data/config/routes.rb CHANGED
@@ -11,6 +11,7 @@ SolidQueueMonitor::Engine.routes.draw do
11
11
  resources :queues, only: [:index]
12
12
 
13
13
  post 'execute_jobs', to: 'scheduled_jobs#create', as: :execute_jobs
14
+ post 'reject_jobs', to: 'scheduled_jobs#reject_all', as: :reject_jobs
14
15
 
15
16
  post 'retry_failed_job/:id', to: 'failed_jobs#retry', as: :retry_failed_job
16
17
  post 'discard_failed_job/:id', to: 'failed_jobs#discard', as: :discard_failed_job
@@ -13,4 +13,11 @@ SolidQueueMonitor.setup do |config|
13
13
 
14
14
  # Number of jobs to display per page
15
15
  # config.jobs_per_page = 25
16
+
17
+ # Auto-refresh settings
18
+ # Enable or disable auto-refresh globally (users can still toggle it in the UI)
19
+ # config.auto_refresh_enabled = true
20
+
21
+ # Auto-refresh interval in seconds (default: 30)
22
+ # config.auto_refresh_interval = 30
16
23
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueMonitor
4
- VERSION = '0.3.1'
4
+ VERSION = '0.4.0'
5
5
  end
@@ -6,13 +6,16 @@ require_relative 'solid_queue_monitor/engine'
6
6
  module SolidQueueMonitor
7
7
  class Error < StandardError; end
8
8
  class << self
9
- attr_accessor :username, :password, :jobs_per_page, :authentication_enabled
9
+ attr_accessor :username, :password, :jobs_per_page, :authentication_enabled,
10
+ :auto_refresh_enabled, :auto_refresh_interval
10
11
  end
11
12
 
12
13
  @username = 'admin'
13
14
  @password = 'password'
14
15
  @jobs_per_page = 25
15
16
  @authentication_enabled = false
17
+ @auto_refresh_enabled = true
18
+ @auto_refresh_interval = 30 # seconds
16
19
 
17
20
  def self.setup
18
21
  yield self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_queue_monitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vishal Sadriya
@@ -45,6 +45,7 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - LICENSE
48
49
  - README.md
49
50
  - Rakefile
50
51
  - app/controllers/solid_queue_monitor/application_controller.rb
@@ -70,6 +71,7 @@ files:
70
71
  - app/services/solid_queue_monitor/failed_job_service.rb
71
72
  - app/services/solid_queue_monitor/html_generator.rb
72
73
  - app/services/solid_queue_monitor/pagination_service.rb
74
+ - app/services/solid_queue_monitor/reject_job_service.rb
73
75
  - app/services/solid_queue_monitor/stats_calculator.rb
74
76
  - app/services/solid_queue_monitor/status_calculator.rb
75
77
  - app/services/solid_queue_monitor/stylesheet_generator.rb