solid_queue_monitor 0.3.2 → 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 +4 -4
- data/README.md +9 -1
- data/app/services/solid_queue_monitor/html_generator.rb +103 -1
- data/app/services/solid_queue_monitor/stylesheet_generator.rb +177 -0
- data/config/initializers/solid_queue_monitor.rb +2 -0
- data/lib/generators/solid_queue_monitor/templates/initializer.rb +7 -0
- data/lib/solid_queue_monitor/version.rb +1 -1
- data/lib/solid_queue_monitor.rb +4 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3bed1180e7bb59bb8741c6fd8607cfcec9e9b604143f4f96c3d4e6f91ae045c6
|
|
4
|
+
data.tar.gz: 5fe88544f47d2146ec5e8034aa476032bba8fbfae2fbb7bf5aa283b66650dfb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c59ad8d48e9414e12e86d848f029b9ad31437b6f41c842b787876ce1ae4a348d35ea6808904305c3212ec29124783b02cc7896a039a0ba7e43625842b127c552
|
|
7
|
+
data.tar.gz: 55554c085582dd57bd884b50bfebaa59e2dffa5758646e49e2744e83ea28b4c7d23d347335c5cd08df0909e312d926a6070371e5474911d52d93256585d7e96f
|
data/README.md
CHANGED
|
@@ -25,6 +25,7 @@ A lightweight, zero-dependency web interface for monitoring Solid Queue backgrou
|
|
|
25
25
|
- **Advanced Job Filtering**: Filter jobs by class name, queue, status, and job arguments
|
|
26
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.
|
|
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
|
|
|
@@ -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
|
-
<
|
|
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
|
|
@@ -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
|
|
@@ -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
|
data/lib/solid_queue_monitor.rb
CHANGED
|
@@ -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
|