sidekiq_queue_manager 1.0.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 +7 -0
- data/.github/workflows/ci.yml +41 -0
- data/INSTALLATION.md +191 -0
- data/README.md +376 -0
- data/app/assets/javascripts/sidekiq_queue_manager/application.js +1836 -0
- data/app/assets/stylesheets/sidekiq_queue_manager/application.css +1018 -0
- data/app/assets/stylesheets/sidekiq_queue_manager/modals.css +838 -0
- data/app/controllers/sidekiq_queue_manager/application_controller.rb +190 -0
- data/app/controllers/sidekiq_queue_manager/assets_controller.rb +87 -0
- data/app/controllers/sidekiq_queue_manager/dashboard_controller.rb +373 -0
- data/app/services/sidekiq_queue_manager/queue_service.rb +475 -0
- data/app/views/layouts/sidekiq_queue_manager/application.html.erb +132 -0
- data/app/views/sidekiq_queue_manager/dashboard/index.html.erb +208 -0
- data/config/routes.rb +48 -0
- data/lib/sidekiq_queue_manager/configuration.rb +157 -0
- data/lib/sidekiq_queue_manager/engine.rb +151 -0
- data/lib/sidekiq_queue_manager/logging_middleware.rb +29 -0
- data/lib/sidekiq_queue_manager/version.rb +12 -0
- data/lib/sidekiq_queue_manager.rb +122 -0
- metadata +227 -0
@@ -0,0 +1,208 @@
|
|
1
|
+
<!-- Theme Toggle Button -->
|
2
|
+
<button id="sqm-theme-toggle" class="sqm-theme-toggle" aria-label="Toggle theme" title="Toggle dark/light mode">
|
3
|
+
<svg class="sqm-theme-icon-light" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
|
4
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636a9 9 0 1011.314 0z" />
|
5
|
+
</svg>
|
6
|
+
<svg class="sqm-theme-icon-dark sqm-hidden" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
|
7
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z" />
|
8
|
+
</svg>
|
9
|
+
</button>
|
10
|
+
|
11
|
+
<!-- Global Statistics -->
|
12
|
+
<section class="sqm-stats-section" aria-label="Global Statistics">
|
13
|
+
<div class="sqm-stats-grid">
|
14
|
+
<div class="sqm-stat-card">
|
15
|
+
<div class="sqm-stat-label">Processed Jobs</div>
|
16
|
+
<div class="sqm-stat-value" id="sqm-processed">0</div>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="sqm-stat-card">
|
20
|
+
<div class="sqm-stat-label">Failed Jobs</div>
|
21
|
+
<div class="sqm-stat-value" id="sqm-failed">0</div>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="sqm-stat-card">
|
25
|
+
<div class="sqm-stat-label">Active Workers</div>
|
26
|
+
<div class="sqm-stat-value" id="sqm-busy">0</div>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div class="sqm-stat-card">
|
30
|
+
<div class="sqm-stat-label">Enqueued Jobs</div>
|
31
|
+
<div class="sqm-stat-value" id="sqm-enqueued">0</div>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</section>
|
35
|
+
|
36
|
+
<!-- Controls Panel -->
|
37
|
+
<section class="sqm-controls-section" aria-label="Queue Controls">
|
38
|
+
<div class="sqm-controls">
|
39
|
+
<div class="sqm-controls-left">
|
40
|
+
<!-- Live Pull Toggle -->
|
41
|
+
<div class="sqm-live-pull-container">
|
42
|
+
<div class="sqm-live-pull-toggle">
|
43
|
+
<svg class="sqm-live-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
44
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 13.5l10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75z" />
|
45
|
+
</svg>
|
46
|
+
<span class="sqm-toggle-text">Live Pull (5s)</span>
|
47
|
+
<div class="sqm-live-status">
|
48
|
+
<div class="sqm-status-dot"></div>
|
49
|
+
<span class="sqm-status-text">OFF</span>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
<button id="sqm-live-toggle-btn" class="sqm-toggle-switch" data-enabled="false" aria-label="Toggle live updates">
|
53
|
+
<div class="sqm-toggle-thumb">
|
54
|
+
<svg class="sqm-toggle-icon sqm-toggle-icon-off" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
55
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
56
|
+
</svg>
|
57
|
+
<svg class="sqm-toggle-icon sqm-toggle-icon-on" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
58
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
59
|
+
</svg>
|
60
|
+
</div>
|
61
|
+
</button>
|
62
|
+
</div>
|
63
|
+
|
64
|
+
<!-- Manual Refresh -->
|
65
|
+
<div class="sqm-refresh-container">
|
66
|
+
<button id="sqm-refresh-btn" class="sqm-refresh-button" title="Manual refresh" aria-label="Manually refresh queue data">
|
67
|
+
<svg class="sqm-refresh-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
68
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
69
|
+
</svg>
|
70
|
+
<span>Refresh</span>
|
71
|
+
</button>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
<div class="sqm-controls-right">
|
76
|
+
<!-- Bulk Actions -->
|
77
|
+
<button id="sqm-pause-all-btn" class="sqm-btn sqm-btn-warning" title="Pause all non-critical queues">
|
78
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
|
79
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
|
80
|
+
</svg>
|
81
|
+
Pause All
|
82
|
+
</button>
|
83
|
+
|
84
|
+
<button id="sqm-resume-all-btn" class="sqm-btn sqm-btn-success" title="Resume all paused queues">
|
85
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
|
86
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />
|
87
|
+
</svg>
|
88
|
+
Resume All
|
89
|
+
</button>
|
90
|
+
</div>
|
91
|
+
</div>
|
92
|
+
</section>
|
93
|
+
|
94
|
+
<!-- Queue Statistics Summary -->
|
95
|
+
<section class="sqm-queue-summary" aria-label="Queue Summary">
|
96
|
+
<div class="sqm-stats-grid" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">
|
97
|
+
<div class="sqm-stat-card">
|
98
|
+
<div class="sqm-stat-label">Total Queues</div>
|
99
|
+
<div class="sqm-stat-value" id="sqm-total-queues">0</div>
|
100
|
+
</div>
|
101
|
+
|
102
|
+
<div class="sqm-stat-card">
|
103
|
+
<div class="sqm-stat-label">Paused Queues</div>
|
104
|
+
<div class="sqm-stat-value" id="sqm-paused-queues">0</div>
|
105
|
+
</div>
|
106
|
+
|
107
|
+
<div class="sqm-stat-card">
|
108
|
+
<div class="sqm-stat-label">Total Jobs</div>
|
109
|
+
<div class="sqm-stat-value" id="sqm-total-jobs">0</div>
|
110
|
+
</div>
|
111
|
+
</div>
|
112
|
+
</section>
|
113
|
+
|
114
|
+
<!-- Loading State -->
|
115
|
+
<div id="sqm-loading" class="sqm-loading">
|
116
|
+
<div class="sqm-loading-spinner" aria-hidden="true"></div>
|
117
|
+
<div class="sqm-loading-text">Loading queue data...</div>
|
118
|
+
</div>
|
119
|
+
|
120
|
+
<!-- Error State -->
|
121
|
+
<div id="sqm-error" class="sqm-error sqm-hidden">
|
122
|
+
<svg class="sqm-error-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
123
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
|
124
|
+
</svg>
|
125
|
+
<h2 class="sqm-error-title">Failed to Load Queue Data</h2>
|
126
|
+
<p class="sqm-error-message" id="sqm-error-message">An error occurred while loading the queue information.</p>
|
127
|
+
<button id="sqm-retry-btn" class="sqm-btn sqm-btn-primary">
|
128
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
|
129
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
130
|
+
</svg>
|
131
|
+
Retry
|
132
|
+
</button>
|
133
|
+
</div>
|
134
|
+
|
135
|
+
<!-- Main Content -->
|
136
|
+
<div id="sqm-content" class="sqm-hidden">
|
137
|
+
<!-- Queues Table -->
|
138
|
+
<section class="sqm-queues-section" aria-label="Queue Details">
|
139
|
+
<div class="sqm-table-container">
|
140
|
+
<table class="sqm-table" id="sqm-queues-table">
|
141
|
+
<thead>
|
142
|
+
<tr>
|
143
|
+
<th scope="col">Queue Name</th>
|
144
|
+
<th scope="col" style="text-align: right;">Size</th>
|
145
|
+
<th scope="col" style="text-align: right;">Workers/Limits</th>
|
146
|
+
<th scope="col" style="text-align: right;">Latency</th>
|
147
|
+
<th scope="col" style="text-align: right;">Actions</th>
|
148
|
+
</tr>
|
149
|
+
</thead>
|
150
|
+
<tbody id="sqm-table-body">
|
151
|
+
<!-- Queue rows will be inserted here by JavaScript -->
|
152
|
+
</tbody>
|
153
|
+
</table>
|
154
|
+
</div>
|
155
|
+
</section>
|
156
|
+
</div>
|
157
|
+
|
158
|
+
<!-- Empty State (when no queues) -->
|
159
|
+
<div id="sqm-empty-state" class="sqm-hidden" style="text-align: center; padding: 3rem 2rem; color: var(--sqm-muted-foreground);">
|
160
|
+
<svg style="width: 4rem; height: 4rem; margin-bottom: 1rem; color: var(--sqm-muted-foreground);" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
161
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5M12 17.25h8.25M3.75 17.25h.01v.01h-.01v-.01ZM7.5 17.25h.01v.01H7.5v-.01ZM11.25 17.25h.01v.01h-.01v-.01Z" />
|
162
|
+
</svg>
|
163
|
+
<h2 style="font-size: 1.25rem; font-weight: 600; color: var(--sqm-muted-foreground); margin-bottom: 1rem;">No Queues Found</h2>
|
164
|
+
<p style="font-size: 0.875rem; margin-bottom: 2rem; line-height: 1.5;">
|
165
|
+
No Sidekiq queues were discovered. Make sure Sidekiq is properly configured and has processed jobs.
|
166
|
+
</p>
|
167
|
+
<button id="sqm-refresh-empty" class="sqm-btn sqm-btn-primary">
|
168
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 1rem; height: 1rem;">
|
169
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99" />
|
170
|
+
</svg>
|
171
|
+
Check Again
|
172
|
+
</button>
|
173
|
+
</div>
|
174
|
+
|
175
|
+
<!-- Accessibility Announcements -->
|
176
|
+
<div id="sqm-announcements" class="sqm-sr-only" aria-live="polite" aria-atomic="true"></div>
|
177
|
+
|
178
|
+
<script type="application/ld+json">
|
179
|
+
{
|
180
|
+
"@context": "https://schema.org",
|
181
|
+
"@type": "WebApplication",
|
182
|
+
"name": "Sidekiq Queue Manager",
|
183
|
+
"description": "Professional Sidekiq queue monitoring and management interface",
|
184
|
+
"applicationCategory": "DeveloperApplication",
|
185
|
+
"operatingSystem": "Web Browser",
|
186
|
+
"offers": {
|
187
|
+
"@type": "Offer",
|
188
|
+
"price": "0",
|
189
|
+
"priceCurrency": "USD"
|
190
|
+
}
|
191
|
+
}
|
192
|
+
</script>
|
193
|
+
|
194
|
+
<!-- Initial Data Script -->
|
195
|
+
<% if @initial_metrics %>
|
196
|
+
<script type="text/javascript">
|
197
|
+
// Provide initial data to reduce initial loading time
|
198
|
+
window.SidekiqQueueManagerInitialData = <%= @initial_metrics.to_json.html_safe %>;
|
199
|
+
</script>
|
200
|
+
<% end %>
|
201
|
+
|
202
|
+
<!-- Configuration Display (for debugging in development) -->
|
203
|
+
<% if Rails.env.development? %>
|
204
|
+
<details style="margin-top: 2rem; padding: 1rem; background: var(--sqm-card); border: 1px solid var(--sqm-border); border-radius: var(--sqm-radius); font-family: var(--sqm-font-mono); font-size: 0.75rem;">
|
205
|
+
<summary style="cursor: pointer; font-weight: 600; color: var(--sqm-muted-foreground);">🔧 Development Configuration</summary>
|
206
|
+
<pre style="margin-top: 1rem; white-space: pre-wrap; color: var(--sqm-muted-foreground); font-size: 0.6875rem;"><%= @configuration.to_yaml if @configuration %></pre>
|
207
|
+
</details>
|
208
|
+
<% end %>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
SidekiqQueueManager::Engine.routes.draw do
|
4
|
+
# Asset serving routes (for apps without asset pipeline)
|
5
|
+
get '/assets/sidekiq_queue_manager/application.css', to: 'assets#css'
|
6
|
+
get '/assets/sidekiq_queue_manager/application.js', to: 'assets#js'
|
7
|
+
get '/assets/sidekiq_queue_manager/modals.css', to: 'assets#modals_css'
|
8
|
+
get '/stylesheets/sidekiq_queue_manager/application.css', to: 'assets#css'
|
9
|
+
get '/stylesheets/sidekiq_queue_manager/modals.css', to: 'assets#modals_css'
|
10
|
+
get '/javascripts/sidekiq_queue_manager/application.js', to: 'assets#js'
|
11
|
+
get '/sidekiq_queue_manager/application.css', to: 'assets#css'
|
12
|
+
get '/sidekiq_queue_manager/application.js', to: 'assets#js'
|
13
|
+
get '/sidekiq_queue_manager/modals.css', to: 'assets#modals_css'
|
14
|
+
|
15
|
+
# Main queue manager interface
|
16
|
+
root 'dashboard#index'
|
17
|
+
|
18
|
+
# Dashboard and metrics
|
19
|
+
get '/', to: 'dashboard#index', as: :dashboard
|
20
|
+
get '/metrics', to: 'dashboard#metrics', as: :metrics
|
21
|
+
|
22
|
+
# Bulk operations (collection actions)
|
23
|
+
post '/queues/pause_all', to: 'dashboard#pause_all', as: :pause_all_queues
|
24
|
+
post '/queues/resume_all', to: 'dashboard#resume_all', as: :resume_all_queues
|
25
|
+
get '/queues/summary', to: 'dashboard#summary', as: :queues_summary
|
26
|
+
|
27
|
+
# Individual queue operations
|
28
|
+
post '/queues/:name/pause', to: 'dashboard#pause_queue', as: :pause_queue
|
29
|
+
post '/queues/:name/resume', to: 'dashboard#resume_queue', as: :resume_queue
|
30
|
+
get '/queues/:name/status', to: 'dashboard#queue_status', as: :queue_status
|
31
|
+
get '/queues/:name/jobs', to: 'dashboard#jobs', as: :queue_jobs
|
32
|
+
delete '/queues/:name/delete_job', to: 'dashboard#delete_job', as: :delete_queue_job
|
33
|
+
post '/queues/:name/clear', to: 'dashboard#clear', as: :clear_queue
|
34
|
+
|
35
|
+
# Queue limits and configuration
|
36
|
+
post '/queues/:name/set_limit', to: 'dashboard#set_limit', as: :set_queue_limit
|
37
|
+
delete '/queues/:name/remove_limit', to: 'dashboard#remove_limit', as: :remove_queue_limit
|
38
|
+
post '/queues/:name/set_process_limit', to: 'dashboard#set_process_limit', as: :set_queue_process_limit
|
39
|
+
delete '/queues/:name/remove_process_limit', to: 'dashboard#remove_process_limit', as: :remove_queue_process_limit
|
40
|
+
|
41
|
+
# Advanced queue operations
|
42
|
+
post '/queues/:name/block', to: 'dashboard#block', as: :block_queue
|
43
|
+
post '/queues/:name/unblock', to: 'dashboard#unblock', as: :unblock_queue
|
44
|
+
|
45
|
+
# Real-time updates via Server-Sent Events
|
46
|
+
get '/live', to: 'dashboard#live_stream', as: :live_stream
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqQueueManager
|
4
|
+
# Configuration class for customizing SidekiqQueueManager behavior
|
5
|
+
# Focus on essential user-configurable options only
|
6
|
+
class Configuration
|
7
|
+
# Essential user-configurable options
|
8
|
+
attr_accessor :authentication_method, :critical_queues, :theme
|
9
|
+
attr_accessor :basic_auth_enabled, :basic_auth_username, :basic_auth_password
|
10
|
+
|
11
|
+
# Advanced options (rarely changed, but available)
|
12
|
+
attr_accessor :refresh_interval, :enable_logging, :enable_caching, :default_queue_priorities
|
13
|
+
|
14
|
+
# Internal options (sensible defaults, not typically user-configured)
|
15
|
+
attr_reader :redis_key_prefix, :redis_timeout, :log_level, :enable_csp, :cache_ttl
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
# Essential defaults (what most users care about)
|
19
|
+
@authentication_method = nil # Custom auth method (optional)
|
20
|
+
@critical_queues = [] # No protected queues by default
|
21
|
+
@theme = 'auto' # Auto light/dark theme
|
22
|
+
|
23
|
+
# Basic HTTP Authentication (professional standard - explicit configuration required)
|
24
|
+
@basic_auth_enabled = true # Secure by default like Sidekiq Web UI
|
25
|
+
@basic_auth_username = 'admin' # Standard admin username
|
26
|
+
@basic_auth_password = nil # MUST be explicitly set by user
|
27
|
+
|
28
|
+
# Advanced defaults (rarely changed)
|
29
|
+
@refresh_interval = 5000 # 5 second UI refresh
|
30
|
+
@enable_logging = true # Log queue operations
|
31
|
+
@enable_caching = true # Cache queue stats for performance
|
32
|
+
@default_queue_priorities = {} # User-defined queue priorities
|
33
|
+
|
34
|
+
# Internal defaults (best practices, rarely modified)
|
35
|
+
@redis_key_prefix = 'sidekiq_queue_manager' # Namespace Redis keys
|
36
|
+
@redis_timeout = 5 # 5 second Redis timeout
|
37
|
+
@log_level = :info # Standard logging level
|
38
|
+
@enable_csp = true # Security headers enabled
|
39
|
+
@cache_ttl = 300 # 5 minute cache TTL
|
40
|
+
end
|
41
|
+
|
42
|
+
# Validate essential user-provided configuration
|
43
|
+
def validate!
|
44
|
+
validate_basic_settings!
|
45
|
+
validate_authentication!
|
46
|
+
self # Return self for method chaining (Ruby idiom)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Check if configuration is valid without raising (Ruby's truthiness approach)
|
50
|
+
def valid?
|
51
|
+
validate!
|
52
|
+
true
|
53
|
+
rescue ConfigurationError
|
54
|
+
false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Export configuration as hash (for JavaScript and internal use)
|
58
|
+
def to_h
|
59
|
+
{
|
60
|
+
# Essential user options
|
61
|
+
authentication_method: @authentication_method,
|
62
|
+
critical_queues: @critical_queues,
|
63
|
+
theme: @theme,
|
64
|
+
basic_auth_enabled: @basic_auth_enabled,
|
65
|
+
basic_auth_username: @basic_auth_username,
|
66
|
+
# NOTE: basic_auth_password excluded for security
|
67
|
+
|
68
|
+
# Advanced options
|
69
|
+
refresh_interval: @refresh_interval,
|
70
|
+
enable_logging: @enable_logging,
|
71
|
+
enable_caching: @enable_caching,
|
72
|
+
default_queue_priorities: @default_queue_priorities,
|
73
|
+
|
74
|
+
# Internal options (included for completeness)
|
75
|
+
redis_key_prefix: @redis_key_prefix,
|
76
|
+
redis_timeout: @redis_timeout,
|
77
|
+
log_level: @log_level,
|
78
|
+
enable_csp: @enable_csp,
|
79
|
+
cache_ttl: @cache_ttl
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Essential configuration summary (for debugging) - more Ruby-like
|
84
|
+
def summary
|
85
|
+
auth_status = if basic_auth_enabled?
|
86
|
+
"basic auth (#{basic_auth_username})"
|
87
|
+
elsif authentication_method
|
88
|
+
"custom (#{authentication_method})"
|
89
|
+
else
|
90
|
+
'disabled'
|
91
|
+
end
|
92
|
+
|
93
|
+
{
|
94
|
+
critical_queues: critical_queues.presence || 'none',
|
95
|
+
theme: theme,
|
96
|
+
authentication: auth_status,
|
97
|
+
caching: enable_caching? ? 'enabled' : 'disabled'
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
# Predicate methods (Ruby convention for boolean checks)
|
102
|
+
def basic_auth_enabled? = @basic_auth_enabled
|
103
|
+
def enable_logging? = @enable_logging
|
104
|
+
def enable_caching? = @enable_caching
|
105
|
+
def enable_csp? = @enable_csp
|
106
|
+
def custom_authentication? = @authentication_method.present?
|
107
|
+
|
108
|
+
# Check if queue is critical (more Ruby-like with inclusion check)
|
109
|
+
def critical_queue?(queue_name)
|
110
|
+
critical_queues.include?(queue_name.to_s)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Get queue priority with Ruby's Hash#fetch for elegant defaults
|
114
|
+
def queue_priority(queue_name)
|
115
|
+
default_queue_priorities.fetch(queue_name.to_s, 1)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# Validate basic configuration settings
|
121
|
+
def validate_basic_settings!
|
122
|
+
# Use Ruby's case statements for more readable validation
|
123
|
+
case refresh_interval
|
124
|
+
when Integer
|
125
|
+
raise ConfigurationError, 'refresh_interval must be positive' unless refresh_interval.positive?
|
126
|
+
else
|
127
|
+
raise ConfigurationError, 'refresh_interval must be a positive Integer'
|
128
|
+
end
|
129
|
+
|
130
|
+
raise ConfigurationError, 'critical_queues must be an Array' unless critical_queues.is_a?(Array)
|
131
|
+
|
132
|
+
raise ConfigurationError, 'theme must be one of: auto, light, dark' unless %w[auto light dark].include?(theme)
|
133
|
+
|
134
|
+
self
|
135
|
+
end
|
136
|
+
|
137
|
+
# Validate authentication configuration (explicit, professional standard)
|
138
|
+
def validate_authentication!
|
139
|
+
# Only validate if basic auth is enabled (explicit choice)
|
140
|
+
return self unless basic_auth_enabled?
|
141
|
+
|
142
|
+
# Use Ruby's presence for more elegant nil/empty checks
|
143
|
+
if basic_auth_password.blank?
|
144
|
+
raise ConfigurationError, 'basic_auth_password must be set when basic_auth_enabled is true. ' \
|
145
|
+
'Set config.basic_auth_password in your initializer or disable with ' \
|
146
|
+
'basic_auth_enabled = false'
|
147
|
+
end
|
148
|
+
|
149
|
+
if basic_auth_username.blank?
|
150
|
+
raise ConfigurationError, 'basic_auth_username cannot be empty when basic_auth_enabled is true'
|
151
|
+
end
|
152
|
+
|
153
|
+
self
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/engine'
|
4
|
+
|
5
|
+
module SidekiqQueueManager
|
6
|
+
# Rails Engine for integrating SidekiqQueueManager with Rails applications
|
7
|
+
#
|
8
|
+
# This engine automatically:
|
9
|
+
# - Adds gem routes to the Rails application
|
10
|
+
# - Registers assets (CSS, JavaScript) with the asset pipeline
|
11
|
+
# - Sets up middleware and initializers
|
12
|
+
# - Provides mountable functionality
|
13
|
+
#
|
14
|
+
class Engine < ::Rails::Engine
|
15
|
+
isolate_namespace SidekiqQueueManager
|
16
|
+
|
17
|
+
# Engine configuration using Ruby's expressive syntax
|
18
|
+
config.generators do |g|
|
19
|
+
g.test_framework :rspec
|
20
|
+
g.fixture_replacement :factory_bot
|
21
|
+
g.assets false
|
22
|
+
g.helper false
|
23
|
+
end
|
24
|
+
|
25
|
+
# Initialize the engine with Ruby's elegant error handling
|
26
|
+
initializer 'sidekiq_queue_manager.assets' do |app|
|
27
|
+
configure_assets(app).tap do |configured|
|
28
|
+
Rails.logger.info asset_configuration_message(configured)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Validate dependencies and configuration on engine load
|
33
|
+
initializer 'sidekiq_queue_manager.validate_dependencies_and_config' do
|
34
|
+
validate_and_configure!
|
35
|
+
rescue SidekiqQueueManager::ConfigurationError => e
|
36
|
+
handle_configuration_error(e)
|
37
|
+
rescue StandardError => e
|
38
|
+
handle_dependency_error(e)
|
39
|
+
end
|
40
|
+
|
41
|
+
initializer 'sidekiq_queue_manager.configure_limit_fetch', before: 'sidekiq' do
|
42
|
+
configure_sidekiq_limit_fetch
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set up middleware for security headers and logging
|
46
|
+
initializer 'sidekiq_queue_manager.middleware' do |app|
|
47
|
+
configure_middleware(app) if SidekiqQueueManager.enable_logging?
|
48
|
+
rescue StandardError => e
|
49
|
+
Rails.logger.warn "[SidekiqQueueManager] Could not configure middleware: #{e.message}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# Configure eager loading paths
|
53
|
+
config.eager_load_paths << File.expand_path('../../app', __dir__)
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Asset configuration with Ruby's functional approach
|
58
|
+
def configure_assets(app)
|
59
|
+
return false unless asset_pipeline_available?(app)
|
60
|
+
|
61
|
+
configure_asset_pipeline(app)
|
62
|
+
rescue StandardError => e
|
63
|
+
Rails.logger.warn "[SidekiqQueueManager] Asset pipeline configuration failed: #{e.message}"
|
64
|
+
false
|
65
|
+
end
|
66
|
+
|
67
|
+
def asset_pipeline_available?(app)
|
68
|
+
app.config.respond_to?(:assets) && defined?(Sprockets)
|
69
|
+
end
|
70
|
+
|
71
|
+
def configure_asset_pipeline(app)
|
72
|
+
app.config.assets.precompile += %w[
|
73
|
+
sidekiq_queue_manager/application.js
|
74
|
+
sidekiq_queue_manager/application.css
|
75
|
+
sidekiq_queue_manager/modals.css
|
76
|
+
]
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def asset_configuration_message(configured)
|
81
|
+
if configured
|
82
|
+
'[SidekiqQueueManager] Assets registered with asset pipeline'
|
83
|
+
else
|
84
|
+
'[SidekiqQueueManager] Asset pipeline not available - using direct asset serving via /assets/* routes'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Dependency validation with Ruby's case pattern matching
|
89
|
+
def validate_and_configure!
|
90
|
+
SidekiqQueueManager.validate_dependencies!
|
91
|
+
SidekiqQueueManager.configuration.validate!
|
92
|
+
Rails.logger.info '[SidekiqQueueManager] Configuration validated successfully'
|
93
|
+
end
|
94
|
+
|
95
|
+
def handle_configuration_error(error)
|
96
|
+
Rails.logger.error "[SidekiqQueueManager] Configuration error: #{error.message}"
|
97
|
+
Rails.logger.error '[SidekiqQueueManager] Please check your config/initializers/sidekiq_queue_manager.rb'
|
98
|
+
# Don't raise in production to prevent app startup failures, but log clearly
|
99
|
+
raise error unless Rails.env.production?
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_dependency_error(error)
|
103
|
+
Rails.logger.error "[SidekiqQueueManager] Dependency validation failed: #{error.message}"
|
104
|
+
# Don't raise in production to prevent app startup failures
|
105
|
+
raise error unless Rails.env.production?
|
106
|
+
end
|
107
|
+
|
108
|
+
# Sidekiq limit fetch configuration using Ruby's method chaining
|
109
|
+
def configure_sidekiq_limit_fetch
|
110
|
+
return configure_existing_limit_fetch if sidekiq_limit_fetch_available?
|
111
|
+
|
112
|
+
attempt_to_load_limit_fetch
|
113
|
+
end
|
114
|
+
|
115
|
+
def sidekiq_limit_fetch_available?
|
116
|
+
defined?(Sidekiq::LimitFetch)
|
117
|
+
end
|
118
|
+
|
119
|
+
def configure_existing_limit_fetch
|
120
|
+
configure_sidekiq_server_options
|
121
|
+
Rails.logger.info '[SidekiqQueueManager] Configured sidekiq-limit_fetch for advanced queue management'
|
122
|
+
end
|
123
|
+
|
124
|
+
def attempt_to_load_limit_fetch
|
125
|
+
require 'sidekiq-limit_fetch'
|
126
|
+
|
127
|
+
if defined?(Sidekiq::LimitFetch)
|
128
|
+
configure_sidekiq_server_options
|
129
|
+
Rails.logger.info '[SidekiqQueueManager] Loaded and configured sidekiq-limit_fetch'
|
130
|
+
end
|
131
|
+
rescue LoadError => e
|
132
|
+
Rails.logger.warn "[SidekiqQueueManager] sidekiq-limit_fetch not available: #{e.message}"
|
133
|
+
Rails.logger.warn '[SidekiqQueueManager] Some advanced queue features may not work'
|
134
|
+
end
|
135
|
+
|
136
|
+
def configure_sidekiq_server_options
|
137
|
+
Sidekiq.configure_server do |config|
|
138
|
+
config.options[:fetch] = Sidekiq::LimitFetch
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Middleware configuration using Ruby's guard clauses
|
143
|
+
def configure_middleware(app)
|
144
|
+
return unless SidekiqQueueManager.configuration.enable_logging?
|
145
|
+
|
146
|
+
app.middleware.use SidekiqQueueManager::LoggingMiddleware
|
147
|
+
Rails.logger.info '[SidekiqQueueManager] Logging middleware enabled'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqQueueManager
|
4
|
+
# Simple logging middleware for SidekiqQueueManager requests
|
5
|
+
class LoggingMiddleware
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
# Only log requests to our gem's paths
|
12
|
+
if env['PATH_INFO'].start_with?('/sidekiq_dashboard')
|
13
|
+
start_time = Time.current
|
14
|
+
|
15
|
+
status, headers, body = @app.call(env)
|
16
|
+
|
17
|
+
end_time = Time.current
|
18
|
+
duration = (end_time - start_time) * 1000 # milliseconds
|
19
|
+
|
20
|
+
Rails.logger.info "[SidekiqQueueManager] #{env['REQUEST_METHOD']} #{env['PATH_INFO']} - #{status} (#{duration.round(2)}ms)"
|
21
|
+
|
22
|
+
[status, headers, body]
|
23
|
+
else
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SidekiqQueueManager
|
4
|
+
# Semantic versioning for the Sidekiq Queue Manager gem
|
5
|
+
#
|
6
|
+
# Version format: MAJOR.MINOR.PATCH
|
7
|
+
# - MAJOR: Incompatible API changes
|
8
|
+
# - MINOR: Add functionality in backward compatible manner
|
9
|
+
# - PATCH: Backward compatible bug fixes
|
10
|
+
VERSION = '1.0.0'
|
11
|
+
end
|
12
|
+
|